@githolon/dsl 0.108.0 → 0.108.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 (94) hide show
  1. package/dart/CHANGELOG.md +5 -0
  2. package/dart/lib/src/application_framework_calls.dart +299 -0
  3. package/dart/lib/src/generated_call.dart +96 -0
  4. package/dart/lib/src/nms_generated_runtime.dart +29 -0
  5. package/dart/lib/src/node_runner_transport.dart +77 -1
  6. package/dart/lib/src/result_usda.dart +487 -0
  7. package/dart/lib/src/runner/node-entry.mjs +6401 -2102
  8. package/dart/lib/src/runner/offline-install.mjs +1538 -163
  9. package/dart/lib/src/runtime_v2_gateway.dart +54 -0
  10. package/dart/lib/src/runtime_v2_transport.dart +117 -0
  11. package/dart/lib/src/session_bound.dart +7 -2
  12. package/dart/lib/src/test_harness.dart +193 -112
  13. package/dart/lib/src/webview_bridge.dart +2120 -569
  14. package/dart/pubspec.yaml +2 -2
  15. package/dart/test/acquire_session_credentials_test.dart +1 -1
  16. package/dart/test/acquire_session_test.dart +68 -14
  17. package/dart/test/adv_realm_interference_test.dart +6 -4
  18. package/dart/test/cluster_births_test.dart +11 -8
  19. package/dart/test/cluster_local_autosync_test.dart +1 -1
  20. package/dart/test/cluster_scale_test.dart +3 -2
  21. package/dart/test/dlq_session_surface_test.dart +52 -35
  22. package/dart/test/first_app_gauntlet_test.dart +1 -1
  23. package/dart/test/local_sync_inert_test.dart +23 -26
  24. package/dart/test/offer_authentication_proof_test.dart +193 -0
  25. package/dart/test/result_usda_test.dart +49 -0
  26. package/dart/test/session_bound_test.dart +8 -11
  27. package/dart/test/stale_bundle_test.dart +18 -3
  28. package/dart/test/test_harness_test.dart +14 -27
  29. package/dart/test/webview_bridge_test.dart +504 -83
  30. package/dart_types/CHANGELOG.md +7 -0
  31. package/dart_types/lib/nomos_kernel_protocol.dart +3 -2
  32. package/dart_types/lib/src/custody.dart +64 -19
  33. package/dart_types/lib/src/local_work_rejected.dart +30 -45
  34. package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pb.dart +169 -0
  35. package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pbenum.dart +11 -0
  36. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +2021 -234
  37. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +175 -8
  38. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +8664 -0
  39. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +369 -0
  40. package/dart_types/lib/src/surfaced.dart +30 -25
  41. package/dart_types/pubspec.yaml +1 -1
  42. package/dart_types/test/surfaced_test.dart +2 -1
  43. package/package.json +1 -1
  44. package/runtime/generated-application-host.js +5736 -2047
  45. package/src/authoring.ts +3 -2
  46. package/src/births.ts +21 -0
  47. package/src/build_package.ts +30 -0
  48. package/src/business_birth.ts +39 -35
  49. package/src/codegen_call_usda.ts +1111 -0
  50. package/src/codegen_dart.ts +207 -282
  51. package/src/codegen_dart_application.ts +195 -252
  52. package/src/codegen_dart_client.ts +1978 -593
  53. package/src/codegen_dart_framework_calls.ts +291 -0
  54. package/src/codegen_dart_scenario.ts +7 -0
  55. package/src/codegen_proof.ts +3 -4
  56. package/src/codegen_provider_dart.ts +21 -7
  57. package/src/codegen_query_usda.ts +214 -0
  58. package/src/codegen_result_usda.ts +685 -0
  59. package/src/codegen_ts.ts +3945 -1111
  60. package/src/compile_package_main.ts +360 -32
  61. package/src/directive.ts +19 -4
  62. package/src/engine_entry.ts +45 -4
  63. package/src/framework/application_home.ts +2 -0
  64. package/src/framework/auth_provider_law.ts +262 -20
  65. package/src/framework/home.ts +32 -28
  66. package/src/framework/owned_custody.ts +19 -11
  67. package/src/framework/owned_custody_value.ts +51 -0
  68. package/src/framework/relations.ts +47 -21
  69. package/src/framework/signing_identity.ts +34 -0
  70. package/src/framework/workspaces.ts +31 -9
  71. package/src/framework_call_definitions.ts +53 -0
  72. package/src/framework_read_definitions.ts +45 -0
  73. package/src/identity_frontier.ts +58 -0
  74. package/src/index.ts +20 -1
  75. package/src/intent.ts +216 -16
  76. package/src/law_check.ts +19 -1
  77. package/src/law_graph.ts +57 -8
  78. package/src/law_graph_usda.ts +73 -5
  79. package/src/law_value_type.ts +106 -19
  80. package/src/read.ts +47 -0
  81. package/src/read_projection_namespace.ts +7 -0
  82. package/src/shape_compat.ts +38 -39
  83. package/src/stable_ids.ts +37 -2
  84. package/src/test_call_usda.ts +83 -0
  85. package/src/wire.ts +36 -2
  86. package/src/wire_encode.ts +45 -1
  87. package/src/workspace_sharing.ts +27 -10
  88. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbjson.dart +0 -6096
  89. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbserver.dart +0 -50
  90. package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbjson.dart +0 -178
  91. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pb.dart +0 -828
  92. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbenum.dart +0 -465
  93. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbjson.dart +0 -573
  94. package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbserver.dart +0 -50
package/dart/pubspec.yaml CHANGED
@@ -7,7 +7,7 @@ repository: https://github.com/Captain-App/nomos2
7
7
  description: >-
8
8
  Nomos 2 Dart client — generated domain values, intent payloads and application
9
9
  lifecycle types over the typed Nomos kernel protocol and USDA custody model.
10
- version: 0.65.0
10
+ version: 0.65.1
11
11
 
12
12
  environment:
13
13
  sdk: ^3.5.0
@@ -16,7 +16,7 @@ dependencies:
16
16
  # The pure-data core (wire types, offer codec, NomosReads, schema validation, error
17
17
  # info). nomos_client depends on and re-exports it, so existing imports are unchanged.
18
18
  # For local monorepo dev, pubspec_overrides.yaml points this at ../dart_types.
19
- nomos_types: ^0.9.0
19
+ nomos_types: ^0.10.0
20
20
  fixnum: ^1.1.1
21
21
  json_schema: ^5.2.2
22
22
  crypto: ^3.0.0
@@ -128,7 +128,7 @@ void main() {
128
128
  reason: 'both concurrent acquirers get the SAME session handle');
129
129
  expect(acquired[0].workspace, 'cloud-opened-ws');
130
130
  final listed = await plane.listWorkspaces();
131
- expect(listed.map((w) => w['session'].toString()),
131
+ expect(listed.map((workspace) => workspace.session),
132
132
  contains('cloud-opened-ws'),
133
133
  reason: 'the acquired workspace is mounted on the plane');
134
134
  });
@@ -6,6 +6,7 @@
6
6
  import 'dart:async';
7
7
 
8
8
  import 'package:nomos_client/src/nms_generated_runtime.dart';
9
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
9
10
  import 'package:test/test.dart';
10
11
 
11
12
  /// A fake runner: answers connect (after a real async gap, so concurrency is observable) and
@@ -13,6 +14,7 @@ import 'package:test/test.dart';
13
14
  class FakeRunner implements BridgeTransport {
14
15
  final _incoming = StreamController<Map<String, Object?>>.broadcast();
15
16
  final List<Map<String, Object?>> sent = [];
17
+ final List<protocol.RuntimeRequest> runtimeRequests = [];
16
18
  final List<String>
17
19
  resident; // workspaces the plane already holds (realm.list())
18
20
 
@@ -25,10 +27,11 @@ class FakeRunner implements BridgeTransport {
25
27
  }
26
28
 
27
29
  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)
30
+ int autoSyncCount(String workspace) => runtimeRequests
31
+ .where((request) =>
32
+ request.whichCommand() ==
33
+ protocol.RuntimeRequest_Command.startAutoSync &&
34
+ request.startAutoSync.session == workspace)
32
35
  .length;
33
36
  Map<String, Object?> connectArgs(int i) =>
34
37
  (sent.where((m) => m['op'] == 'connect').elementAt(i)['args'] as Map)
@@ -37,6 +40,56 @@ class FakeRunner implements BridgeTransport {
37
40
  @override
38
41
  Stream<Map<String, Object?>> get messages => _incoming.stream;
39
42
 
43
+ @override
44
+ Future<protocol.RuntimeResponse> invokeRuntime(
45
+ protocol.RuntimeRequest request,
46
+ ) async {
47
+ runtimeRequests.add(request.deepCopy());
48
+ final response = protocol.RuntimeResponse(
49
+ protocolVersion: 2,
50
+ requestId: request.requestId,
51
+ );
52
+ switch (request.whichCommand()) {
53
+ case protocol.RuntimeRequest_Command.listWorkspaces:
54
+ final connected = sent
55
+ .where((message) => message['op'] == 'connect')
56
+ .map(
57
+ (message) => ((message['args'] as Map)['workspace']).toString())
58
+ .toSet();
59
+ response.workspaceList = protocol.WorkspaceList(
60
+ workspaces: [
61
+ for (final workspace in {...resident, ...connected})
62
+ protocol.WorkspaceSummary(
63
+ session: workspace,
64
+ workspace: workspace,
65
+ residency: connected.contains(workspace)
66
+ ? protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_MOUNTED
67
+ : protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_PARKED,
68
+ syncPosture: protocol
69
+ .WorkspaceSyncPosture.WORKSPACE_SYNC_POSTURE_LOCAL_ONLY,
70
+ ),
71
+ ],
72
+ );
73
+ break;
74
+ case protocol.RuntimeRequest_Command.startAutoSync:
75
+ response.autoSyncStarted =
76
+ protocol.AutoSyncStarted(session: request.startAutoSync.session);
77
+ break;
78
+ case protocol.RuntimeRequest_Command.stopAutoSync:
79
+ response.autoSyncStopped =
80
+ protocol.AutoSyncStopped(session: request.stopAutoSync.session);
81
+ break;
82
+ case protocol.RuntimeRequest_Command.closeRealm:
83
+ response.realmClosed = protocol.RealmClosed();
84
+ break;
85
+ default:
86
+ throw StateError(
87
+ 'FakeRunner does not implement ${request.whichCommand().name}',
88
+ );
89
+ }
90
+ return response;
91
+ }
92
+
40
93
  @override
41
94
  void send(Map<String, Object?> message) {
42
95
  sent.add(message);
@@ -74,8 +127,7 @@ class FakeRunner implements BridgeTransport {
74
127
  result = {
75
128
  'workspace': args['workspace'],
76
129
  'head': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
77
- if (args['ifHead'] ==
78
- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
130
+ if (args['ifHead'] == 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
79
131
  'unchanged': true
80
132
  else
81
133
  'snapshot': 'resident-snapshot-b64',
@@ -390,16 +442,20 @@ void main() {
390
442
 
391
443
  // Before any connect: the plane-resident (parked) workspace surfaces with an explicit non-connected state.
392
444
  var list = await bridge.listWorkspaces();
393
- final before = list.firstWhere((w) => w['workspace'] == 'estate');
394
- expect(before['state'], 'parked',
445
+ final before =
446
+ list.firstWhere((workspace) => workspace.workspace == 'estate');
447
+ expect(before.residency,
448
+ protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_PARKED,
395
449
  reason:
396
450
  'parked custody is not connected — the app must acquire it first');
397
451
 
398
452
  // After connecting it, the SAME workspace reports connected.
399
453
  await bridge.connect(workspace: 'estate', session: 'estate');
400
454
  list = await bridge.listWorkspaces();
401
- final after = list.firstWhere((w) => w['workspace'] == 'estate');
402
- expect(after['state'], 'connected');
455
+ final after =
456
+ list.firstWhere((workspace) => workspace.workspace == 'estate');
457
+ expect(after.residency,
458
+ protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_MOUNTED);
403
459
  await bridge.dispose();
404
460
  });
405
461
 
@@ -410,8 +466,7 @@ void main() {
410
466
  final bridge = NomosBridge(runner);
411
467
  await bridge.ready;
412
468
 
413
- final saved =
414
- await bridge.nomosInternalSnapshotWorkspace('born-child');
469
+ final saved = await bridge.nomosInternalSnapshotWorkspace('born-child');
415
470
  expect(saved.workspace, 'born-child');
416
471
  expect(saved.head, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
417
472
  expect(saved.snapshot, 'resident-snapshot-b64');
@@ -420,8 +475,7 @@ void main() {
420
475
  reason:
421
476
  'save-all must not acquire an unopened child or invent host authority');
422
477
 
423
- final unchanged = await bridge.nomosInternalSnapshotWorkspace(
424
- 'born-child',
478
+ final unchanged = await bridge.nomosInternalSnapshotWorkspace('born-child',
425
479
  ifHead: saved.head);
426
480
  expect(unchanged.unchanged, isTrue);
427
481
  expect(unchanged.snapshot, isNull);
@@ -21,6 +21,7 @@ import 'dart:convert';
21
21
  import 'dart:io';
22
22
 
23
23
  import 'package:nomos_client/src/nms_conformance_runtime.dart';
24
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
24
25
  import 'package:nomos_types/src/nomos_ref.dart' show NmsInternalNomosRefs;
25
26
  import 'package:test/test.dart';
26
27
 
@@ -405,12 +406,13 @@ void main() {
405
406
  actor: 'adv-owner',
406
407
  );
407
408
  expect(outcome.born, hasLength(1));
408
- expect(outcome.birthOutcomes.first['status'], 'born');
409
+ expect(outcome.birthOutcomes.first.status,
410
+ NomosBirthOutcomeStatus.BIRTH_OUTCOME_STATUS_BORN);
409
411
  final child = outcome.born.single;
410
412
 
411
413
  // the child is resident BEFORE any session opens it — listWorkspaces sees it
412
414
  final listedPre = await plane.listWorkspaces();
413
- expect(listedPre.map((w) => w['workspace']), contains(child),
415
+ expect(listedPre.map((workspace) => workspace.workspace), contains(child),
414
416
  reason:
415
417
  'a born child is resident on the plane before it is connected');
416
418
 
@@ -552,8 +554,8 @@ void main() {
552
554
  restoreFrom: snaps['lru-3'],
553
555
  allowUnborn: true);
554
556
  final listed = await bridge.listWorkspaces();
555
- final l1 = listed.firstWhere((w) => w['workspace'] == 'lru-1');
556
- expect(l1['parked'], isTrue,
557
+ final l1 = listed.firstWhere((workspace) => workspace.workspace == 'lru-1');
558
+ expect(l1.residency, protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_PARKED,
557
559
  reason: 'lru-1 should be LRU-parked: $listed');
558
560
 
559
561
  // (a) session ops transparently remount from the parked snapshot, state intact
@@ -98,8 +98,8 @@ void main() {
98
98
  reason: 'the born workspace is estate-<sha256(owner)>');
99
99
  expect(outcome.birthOutcomes, isNotEmpty,
100
100
  reason: 'the typed birth verdict rides beside born');
101
- expect(outcome.birthOutcomes.first['workspace'], outcome.born.single);
102
- expect(outcome.birthOutcomes.first['status'], isNotNull);
101
+ expect(outcome.birthOutcomes.first.workspace, outcome.born.single);
102
+ expect(outcome.birthOutcomes.first.hasStatus(), isTrue);
103
103
 
104
104
  // The child handle is addressable on the plane (the loop that OPENS + reads it is the dedicated test
105
105
  // below — 'born child OPENS as a sibling session ...').
@@ -121,9 +121,10 @@ void main() {
121
121
  // child genesis folds clean and the born verdict is `born` — hard-asserted, no skip.
122
122
  expect(outcome.born, hasLength(1));
123
123
  expect(outcome.birthOutcomes, hasLength(1));
124
- expect(outcome.birthOutcomes.first['status'], 'born',
124
+ expect(outcome.birthOutcomes.first.status,
125
+ NomosBirthOutcomeStatus.BIRTH_OUTCOME_STATUS_BORN,
125
126
  reason: 'the child genesis (installDomain×2 + seed) folded through the child\'s own gate offline');
126
- expect(outcome.birthOutcomes.first['workspace'], outcome.born.single);
127
+ expect(outcome.birthOutcomes.first.workspace, outcome.born.single);
127
128
  } finally {
128
129
  await harness.dispose();
129
130
  }
@@ -138,7 +139,8 @@ void main() {
138
139
  }
139
140
  final (harness, outcome) = await _birth();
140
141
  try {
141
- expect(outcome.birthOutcomes.first['status'], 'born',
142
+ expect(outcome.birthOutcomes.first.status,
143
+ NomosBirthOutcomeStatus.BIRTH_OUTCOME_STATUS_BORN,
142
144
  reason: 'precondition: the child is born on the local plane');
143
145
  final child = outcome.born.single;
144
146
 
@@ -159,7 +161,7 @@ void main() {
159
161
 
160
162
  // listWorkspaces now enumerates BOTH the parent and the newly-opened born child on the one plane.
161
163
  final listed = await harness.bridge.listWorkspaces();
162
- final sessions = listed.map((w) => w['session'].toString()).toSet();
164
+ final sessions = listed.map((workspace) => workspace.session).toSet();
163
165
  expect(sessions, contains(child),
164
166
  reason: 'listWorkspaces surfaces the born child as a live session');
165
167
  expect(sessions, contains('estate-birth'),
@@ -188,9 +190,10 @@ void main() {
188
190
  } on BirthIncomplete catch (e) {
189
191
  expect(e.failedBirths, isNotEmpty,
190
192
  reason: 'a failed birth still reports its typed per-child verdicts');
191
- expect(e.failedBirths.first['status'], isNot('born'),
193
+ expect(e.failedBirths.first.status,
194
+ isNot(NomosBirthOutcomeStatus.BIRTH_OUTCOME_STATUS_BORN),
192
195
  reason: 'an unresolvable framework hash cannot yield a born child');
193
- expect((e.failedBirths.first['reason'] ?? '').toString(), isNotEmpty,
196
+ expect(e.failedBirths.first.reason, isNotEmpty,
194
197
  reason: 'the failure carries a human-legible reason (never a silent drop)');
195
198
  } finally {
196
199
  await harness?.dispose();
@@ -80,7 +80,7 @@ void main() {
80
80
  const Duration(seconds: 10),
81
81
  onTimeout: () => throw StateError(
82
82
  'listWorkspaces() hung after a local-only autoSync (dispatcher monopolized)'));
83
- expect(listed.map((w) => w['session'].toString()).toSet(),
83
+ expect(listed.map((workspace) => workspace.session).toSet(),
84
84
  containsAll(names.toSet()));
85
85
  final sibling = await harness
86
86
  .session('estate-a')
@@ -12,6 +12,7 @@ library;
12
12
  import 'dart:io';
13
13
 
14
14
  import 'package:nomos_client/src/nms_conformance_runtime.dart';
15
+ import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
15
16
  import 'package:test/test.dart';
16
17
 
17
18
  String get _root => '${Directory.current.path}/../..';
@@ -98,9 +99,9 @@ void main() {
98
99
 
99
100
  // listWorkspaces enumerates the whole local plane — every one of the N is present + mounted.
100
101
  final listed = await harness.bridge.listWorkspaces();
101
- expect(listed.map((w) => w['session'].toString()).toSet(), names.toSet(),
102
+ expect(listed.map((workspace) => workspace.session).toSet(), names.toSet(),
102
103
  reason: 'listWorkspaces surfaces all $_n live workspaces');
103
- expect(listed.every((w) => w['mounted'] == true), isTrue,
104
+ expect(listed.every((workspace) => workspace.residency == protocol.WorkspaceResidency.WORKSPACE_RESIDENCY_MOUNTED), isTrue,
104
105
  reason: 'every listed workspace is mounted');
105
106
 
106
107
  // Wall-clock sanity — dozens of live workspaces must open + drive in a CI-sane budget, not minutes.
@@ -20,15 +20,18 @@ import 'package:test/test.dart';
20
20
 
21
21
  String get _root => '${Directory.current.path}/../..';
22
22
  String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
23
- String get _geoDeploy => '$_root/bench/scale/fixtures/estate-geo/build/estate-geo.law.usdz';
24
- String get _docsDeploy => '$_root/bench/scale/fixtures/surfaced/build/surfaced.law.usdz';
23
+ String get _geoDeploy =>
24
+ '$_root/bench/scale/fixtures/estate-geo/build/estate-geo.law.usdz';
25
+ String get _docsDeploy =>
26
+ '$_root/bench/scale/fixtures/surfaced/build/surfaced.law.usdz';
25
27
  String get _runtimeDir =>
26
28
  Platform.environment['NOMOS_DART_RUNTIME'] ??
27
29
  Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
28
30
  '${Platform.environment['HOME'] ?? ''}/.holon/runtime';
29
31
 
30
32
  String? _missingPrereq() {
31
- if (!File(_repoEntry).existsSync()) return 'in-repo runner not found at $_repoEntry';
33
+ if (!File(_repoEntry).existsSync())
34
+ return 'in-repo runner not found at $_repoEntry';
32
35
  for (final e in {
33
36
  _geoDeploy: 'bench/scale/fixtures/estate-geo',
34
37
  _docsDeploy: 'bench/scale/fixtures/surfaced',
@@ -55,12 +58,14 @@ Future<T> _leg<T>(String name, Future<T> Function() body) async {
55
58
  print('✔ DLQ $name (${sw.elapsedMilliseconds}ms)');
56
59
  return r;
57
60
  } catch (e, st) {
58
- throw TestFailure('DLQ $name FAILED after ${sw.elapsedMilliseconds}ms:\n$e\n$st');
61
+ throw TestFailure(
62
+ 'DLQ $name FAILED after ${sw.elapsedMilliseconds}ms:\n$e\n$st');
59
63
  }
60
64
  }
61
65
 
62
66
  void main() {
63
- test('DLQ: park → surface → refused retry → law fix → retry admits → discard drains',
67
+ test(
68
+ 'DLQ: park → surface → refused retry → law fix → retry admits → discard drains',
64
69
  () async {
65
70
  final missing = _missingPrereq();
66
71
  if (missing != null) {
@@ -69,7 +74,9 @@ void main() {
69
74
  }
70
75
  final harness = await NomosTestHarness.cluster(
71
76
  workspaces: {
72
- 'writer': [_geoDeploy], // holds the estate law — where the work is authored
77
+ 'writer': [
78
+ _geoDeploy
79
+ ], // holds the estate law — where the work is authored
73
80
  'app': [_docsDeploy], // the jammed workspace — NO estate law yet
74
81
  },
75
82
  nodeEntry: _repoEntry,
@@ -83,7 +90,8 @@ void main() {
83
90
 
84
91
  // ── LEG 1: author REAL work under the estate law, and read its full intent bytes back
85
92
  // through the production salvage lane. ──
86
- final work = await _leg('leg 1: author two writes + salvage their intent bytes', () async {
93
+ final work = await _leg(
94
+ 'leg 1: author two writes + salvage their intent bytes', () async {
87
95
  Future<void> place(String name) => writer.offerOutcome(
88
96
  domain: 'estate',
89
97
  directiveId: 'placeSite',
@@ -106,26 +114,32 @@ void main() {
106
114
  .where((w) {
107
115
  try {
108
116
  final doc =
109
- jsonDecode(utf8.decode(base64Decode(w['changeB64'].toString()))) as Map;
117
+ jsonDecode(utf8.decode(base64Decode(w['changeB64'].toString())))
118
+ as Map;
110
119
  return (doc['payload'] as Map?)?['domain'] == 'estate';
111
120
  } catch (_) {
112
121
  return false;
113
122
  }
114
123
  }).toList();
115
124
  expect(pending, hasLength(2),
116
- reason: 'salvage() returns the authored estate intents with their full bytes');
117
- expect(pending.every((w) => (w['id'] ?? '').toString().isNotEmpty), isTrue);
125
+ reason:
126
+ 'salvage() returns the authored estate intents with their full bytes');
127
+ expect(pending.every((w) => (w['id'] ?? '').toString().isNotEmpty),
128
+ isTrue);
118
129
  return pending;
119
130
  });
120
131
 
121
132
  // ── LEG 2: park both on the jammed workspace (the shape a domain rejection parks) and
122
133
  // assert deadLetters() surfaces them with full provenance. ──
123
- await _leg('leg 2: park as dead letters → deadLetters() surfaces provenance', () async {
134
+ await _leg(
135
+ 'leg 2: park as dead letters → deadLetters() surfaces provenance',
136
+ () async {
124
137
  final r = await app.absorbSalvage(deadLetters: [
125
138
  for (final w in work)
126
139
  {
127
140
  'id': w['id'],
128
- 'oid': w['id'], // provenance slot; the entry is deduped/looked up by id
141
+ 'oid': w[
142
+ 'id'], // provenance slot; the entry is deduped/looked up by id
129
143
  'changeB64': w['changeB64'],
130
144
  'source': 'local',
131
145
  'error': "no active installed law declares domain 'estate'",
@@ -135,32 +149,32 @@ void main() {
135
149
  final dead = await app.deadLetters();
136
150
  expect(dead, hasLength(2));
137
151
  final entry = dead.first;
138
- expect(entry['domain'], 'estate',
139
- reason: 'the DLQ decodes domain/directiveId provenance from the parked bytes');
140
- expect(entry['directiveId'], 'placeSite');
141
- expect(entry['source'], 'local');
142
- expect(entry.containsKey('changeB64'), isFalse,
143
- reason: 'deadLetters() is metadata-only; the bytes stay in the durable queue');
152
+ expect(entry.domain, 'estate',
153
+ reason:
154
+ 'the DLQ decodes domain/directiveId provenance from the parked bytes');
155
+ expect(entry.directive, 'placeSite');
156
+ expect(entry.source, 'local');
144
157
  });
145
158
 
146
159
  // ── LEG 3: retry while the law is still absent — a REFUSAL, not a loss (and, regression:
147
160
  // not a "no remote main" throw on this local-only session). ──
148
161
  final firstId = work.first['id'].toString();
149
- await _leg('leg 3: retry refuses while the law is absent; the entry stays', () async {
162
+ await _leg(
163
+ 'leg 3: retry refuses while the law is absent; the entry stays',
164
+ () async {
150
165
  final r = await app.retryDeadLetter(firstId);
151
- expect(r['ok'], isFalse, reason: 'the law still cannot merge it: $r');
166
+ expect(r.retried, isFalse, reason: 'the law still cannot merge it: $r');
152
167
  final dead = await app.deadLetters();
153
- expect(dead, hasLength(2), reason: 'a refused retry NEVER drops the work');
154
- expect(
155
- dead
156
- .firstWhere((d) => d['id'] == firstId)['error']
157
- .toString(),
168
+ expect(dead, hasLength(2),
169
+ reason: 'a refused retry NEVER drops the work');
170
+ expect(dead.firstWhere((d) => d.hasId() && d.id == firstId).message,
158
171
  isNotEmpty,
159
172
  reason: 'the entry carries the fresh refusal');
160
173
  });
161
174
 
162
175
  // ── LEG 4: the domain dev ships the law fix; retry UNJAMS and the work lands, readable. ──
163
- await _leg('leg 4: law fix (installDomain) → retry admits → the work is on the ledger',
176
+ await _leg(
177
+ 'leg 4: law fix (installDomain) → retry admits → the work is on the ledger',
164
178
  () async {
165
179
  final usda = (jsonDecode(File(_geoDeploy).readAsStringSync())
166
180
  as Map)['packageUsda']
@@ -178,24 +192,27 @@ void main() {
178
192
  domainHash: harness.frameworkHash,
179
193
  );
180
194
  final r = await app.retryDeadLetter(firstId);
181
- expect(r['ok'], isTrue, reason: 'the unjam after the law fix: $r');
195
+ expect(r.retried, isTrue, reason: 'the unjam after the law fix: $r');
182
196
  final dead = await app.deadLetters();
183
- expect(dead, hasLength(1), reason: 'the retried entry resolved out of the queue');
184
- final rows =
185
- await app.query('sitesByName', {'name': 'a day of work I refuse to lose'});
186
- expect(rows, hasLength(1), reason: "the user's work landed and reads back");
197
+ expect(dead, hasLength(1),
198
+ reason: 'the retried entry resolved out of the queue');
199
+ final rows = await app
200
+ .query('sitesByName', {'name': 'a day of work I refuse to lose'});
201
+ expect(rows, hasLength(1),
202
+ reason: "the user's work landed and reads back");
187
203
  });
188
204
 
189
205
  // ── LEG 5: discard is the app's explicit drop; the queue drains; a re-retry of a gone id
190
206
  // answers a typed refusal. ──
191
- await _leg('leg 5: discard drains; retry of a gone id refuses by name', () async {
207
+ await _leg('leg 5: discard drains; retry of a gone id refuses by name',
208
+ () async {
192
209
  final secondId = work[1]['id'].toString();
193
210
  final d = await app.discardDeadLetter(secondId);
194
- expect(d['ok'], isTrue);
211
+ expect(d.removed, 1);
195
212
  expect(await app.deadLetters(), isEmpty);
196
213
  final gone = await app.retryDeadLetter(secondId);
197
- expect(gone['ok'], isFalse);
198
- expect(gone['error'].toString(), contains('no dead letter'));
214
+ expect(gone.retried, isFalse);
215
+ expect(gone.message, contains('no dead letter'));
199
216
  });
200
217
  } finally {
201
218
  await harness.dispose().timeout(const Duration(seconds: 30),
@@ -251,7 +251,7 @@ void main() {
251
251
  const Duration(seconds: 10),
252
252
  onTimeout: () => throw StateError(
253
253
  'listWorkspaces() hung after a local-only autoSync'));
254
- expect(listed.map((w) => w['session'].toString()).toSet(),
254
+ expect(listed.map((workspace) => workspace.session).toSet(),
255
255
  containsAll({'home', 'docs-ws', _keyed, 'reader'}));
256
256
  await home.stopAutoSync().timeout(const Duration(seconds: 10),
257
257
  onTimeout: () => throw StateError(
@@ -5,12 +5,14 @@
5
5
  @TestOn('vm')
6
6
  library;
7
7
 
8
+ import 'dart:convert';
8
9
  import 'dart:io';
9
10
 
10
11
  import 'package:nomos_client/src/nms_conformance_runtime.dart';
12
+ import 'package:crypto/crypto.dart' show sha256;
11
13
  import 'package:test/test.dart';
12
14
 
13
- import '../../../bench/scale/fixtures/estate-birth/build/dart/src/generated/home.application.dart';
15
+ import '../../../bench/scale/fixtures/estate-birth/build/dart/src/generated/estate.application.dart';
14
16
 
15
17
  String get _root => '${Directory.current.path}/../..';
16
18
  String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
@@ -54,30 +56,24 @@ void main() {
54
56
  );
55
57
  try {
56
58
  final session = harness.session(_workspace);
57
- // This is a nomos_client runtime regression test, so the raw offer below is
58
- // deliberate infrastructure setup. Tenant code imports the generated package
59
- // application; it cannot import the private generated client implementation.
60
- await nomosInternalBindHomeApplication(session, _owner);
59
+ final application =
60
+ await nomosInternalBindEstateApplication(session, _owner);
61
61
 
62
- // 1-2. offer a write on the snapshot-opened local session.
63
- final facts = BirthEstateWorkspacePayload(
62
+ // 1-2. Offer a normal generated business call on the snapshot-opened local
63
+ // session. This test is about local sync, not the separate child-workspace
64
+ // birth ceremony.
65
+ final bornAt = DateTime.now().toUtc().toIso8601String();
66
+ final facts = SeedEstateWorkspacePayload(
64
67
  owner: _owner,
65
- frameworkHash: harness.frameworkHash!,
66
- lawHash: harness.lawHashes(_workspace).first,
67
- bornAt: DateTime.now().toUtc().toIso8601String(),
68
+ estateName:
69
+ 'estate-${sha256.convert(utf8.encode(_owner)).toString().substring(0, 40)}',
70
+ bornAt: bornAt,
68
71
  );
69
- final birth = await session.offerOutcome(
70
- domain: 'home',
71
- directiveId: 'birthEstateWorkspace',
72
- payload: facts.nmsInternalToPayloadJson(),
73
- actor: _owner,
74
- );
75
- expect(birth.born, hasLength(1));
72
+ await application.estateOwners.seedEstateWorkspace(facts);
76
73
 
77
- // 3. Infrastructure assertion: inspect Nomos's private birth record through the raw harness session.
78
- // Generated tenant clients deliberately do not expose this framework query.
74
+ // 3. Read through the generated, typed application surface.
79
75
  final before =
80
- await session.query('birthRecordsByPrincipal', {'principal': _owner});
76
+ await application.estateOwners.estatesByOwner(owner: _owner);
81
77
  expect(before, hasLength(1),
82
78
  reason: 'the offer must be readable BEFORE sync');
83
79
  final statusBefore = await session.custodyStatus();
@@ -86,7 +82,7 @@ void main() {
86
82
 
87
83
  // 4. local-only sync — must be a no-op in EVERY observable respect.
88
84
  final summary = await session.sync(admit: true);
89
- expect(summary['localOnly'], true,
85
+ expect(summary.localOnly, true,
90
86
  reason: 'a local:// session syncs as the documented no-op');
91
87
 
92
88
  // 5. THE REGRESSION (co2): custody unchanged, same query returns the SAME row.
@@ -95,14 +91,15 @@ void main() {
95
91
  reason:
96
92
  'local-only sync must not consume/move pending local intents');
97
93
  final after =
98
- await session.query('birthRecordsByPrincipal', {'principal': _owner});
94
+ await application.estateOwners.estatesByOwner(owner: _owner);
99
95
  expect(after, hasLength(1),
100
96
  reason:
101
97
  'local-only sync must be observationally inert — the row must still read');
102
- final beforeData = ((before.single as Map)['data'] as Map);
103
- final afterData = ((after.single as Map)['data'] as Map);
104
- expect(afterData['principal'], beforeData['principal'],
105
- reason: 'the SAME row, byte-for-byte semantics');
98
+ expect(after.single.owner, before.single.owner,
99
+ reason: 'the SAME typed business record remains visible');
100
+ expect(after.single.estateName, before.single.estateName);
101
+ expect(after.single.bornAt, before.single.bornAt);
102
+ expect(after.single.ref.toString(), before.single.ref.toString());
106
103
  } finally {
107
104
  await harness.dispose();
108
105
  }