@githolon/dsl 0.100.6 → 0.101.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 (82) hide show
  1. package/dart/CHANGELOG.md +6 -0
  2. package/dart/lib/src/dev_cloud.dart +46 -15
  3. package/dart/lib/src/test_harness.dart +78 -16
  4. package/dart/lib/src/webview_bridge.dart +640 -126
  5. package/dart/pubspec.lock +1 -1
  6. package/dart/pubspec.yaml +2 -2
  7. package/dart/test/bundle_version_pairing_test.dart +11 -22
  8. package/dart/test/co2_product_gauntlet_test.dart +228 -0
  9. package/dart/test/errors_test.dart +63 -12
  10. package/dart/test/first_app_gauntlet_test.dart +29 -3
  11. package/dart/test/webview_bridge_test.dart +274 -0
  12. package/dart_types/CHANGELOG.md +6 -0
  13. package/dart_types/lib/src/errors.dart +136 -30
  14. package/dart_types/lib/src/surfaced.dart +48 -2
  15. package/dart_types/lib/src/wire.dart +3 -1
  16. package/dart_types/pubspec.yaml +1 -1
  17. package/dart_types/test/surfaced_test.dart +19 -0
  18. package/package.json +1 -1
  19. package/src/activity.ts +43 -4
  20. package/src/aggregate.ts +132 -22
  21. package/src/attested_core.ts +1 -1
  22. package/src/authoring.ts +119 -6
  23. package/src/birth_meter.ts +4 -4
  24. package/src/birth_meter_ensure.ts +9 -1
  25. package/src/births.ts +53 -22
  26. package/src/build_package.ts +55 -14
  27. package/src/business_birth.ts +48 -7
  28. package/src/codegen_dart.ts +267 -37
  29. package/src/codegen_dart_client.ts +921 -82
  30. package/src/codegen_openapi.ts +28 -5
  31. package/src/codegen_proof.ts +107 -11
  32. package/src/codegen_ts.ts +961 -135
  33. package/src/compile_package_main.ts +155 -45
  34. package/src/conflicts.ts +252 -0
  35. package/src/describe_model.ts +448 -0
  36. package/src/directive.ts +5 -38
  37. package/src/drivers.ts +12 -4
  38. package/src/engine_entry.ts +4 -0
  39. package/src/evolve_preview.ts +72 -11
  40. package/src/fields.ts +201 -55
  41. package/src/framework/application_home.ts +103 -0
  42. package/src/framework/application_home_domain.ts +2 -0
  43. package/src/framework/attested_receipts.ts +2 -2
  44. package/src/framework/auth_provider_law.ts +51 -24
  45. package/src/framework/business_corrections.ts +170 -0
  46. package/src/framework/disclosure.ts +1 -1
  47. package/src/framework/domain_lifecycle.ts +250 -16
  48. package/src/framework/encryption_identity.ts +1 -1
  49. package/src/framework/encryption_scope.ts +21 -13
  50. package/src/framework/era_certification.ts +1 -1
  51. package/src/framework/framework_aggregates.ts +2 -0
  52. package/src/framework/genesis.ts +1 -1
  53. package/src/framework/home.ts +22 -9
  54. package/src/framework/identity.ts +5 -5
  55. package/src/framework/impure_capability.ts +2 -2
  56. package/src/framework/owned_custody.ts +561 -0
  57. package/src/framework/platform_admin.ts +1 -1
  58. package/src/framework/relations.ts +9 -1
  59. package/src/framework/repair.ts +1 -1
  60. package/src/framework/signing_identity.ts +2 -2
  61. package/src/framework/source_roster.ts +1 -1
  62. package/src/framework/workspaces.ts +17 -10
  63. package/src/generated_client_names.ts +39 -0
  64. package/src/index.ts +20 -15
  65. package/src/kernel_capabilities.ts +25 -1
  66. package/src/law_check.ts +4 -1
  67. package/src/manifest.ts +356 -63
  68. package/src/read_projection_manifest.ts +8 -0
  69. package/src/read_projection_namespace.ts +4 -0
  70. package/src/relation.ts +1 -1
  71. package/src/shape_compat.ts +47 -2
  72. package/src/stable_ids.ts +38 -6
  73. package/src/stable_ids_types.ts +30 -0
  74. package/src/surfaced_lint.ts +0 -0
  75. package/src/usd.ts +62 -11
  76. package/src/usd_layers.ts +54 -3
  77. package/src/value_object.ts +23 -19
  78. package/src/wire.ts +1 -0
  79. package/src/wire_encode.ts +48 -4
  80. package/src/workspace_routing.ts +57 -0
  81. package/src/workspace_sharding.ts +9 -9
  82. package/src/certified_read.ts +0 -313
@@ -136,6 +136,88 @@ class FakeRunner implements BridgeTransport {
136
136
  case 'lawStatus':
137
137
  _incoming.add(reply({'installed': true, 'drifted': false}));
138
138
  break;
139
+ case 'cryptoOwnedDeviceRequest':
140
+ _incoming.add(reply({'request': 'opaque-owned-device-request'}));
141
+ break;
142
+ case 'cryptoOwnedPrepareApproval':
143
+ _incoming.add(reply({
144
+ 'newKeySubject': 'key:${'a' * 64}',
145
+ 'ownedCustody': 'opaque-owned-custody',
146
+ 'verifiedVia': 'nomos:test-root',
147
+ 'validFromEpoch': 0,
148
+ 'approvedAt': '2026-07-18T14:20:00.000Z',
149
+ }));
150
+ break;
151
+ case 'cryptoOwnedScopeKeys':
152
+ _incoming.add(reply({
153
+ 'keys': [
154
+ {
155
+ 'scope': 'notes.Note:note-1#owned-reader',
156
+ 'epoch': 2,
157
+ 'key': 'opaque-record-key',
158
+ }
159
+ ]
160
+ }));
161
+ break;
162
+ case 'cryptoOwnedEnsureIdentity':
163
+ _incoming.add(reply({'ready': true}));
164
+ break;
165
+ case 'cryptoOwnedPrepareRecordAction':
166
+ _incoming.add(reply({
167
+ 'recordId': 'note-1',
168
+ 'peer': 'user:bob',
169
+ 'epoch': 2,
170
+ 'wraps': <Object?>[],
171
+ }));
172
+ break;
173
+ case 'adoptGeneratedModel':
174
+ _incoming.add(reply({
175
+ 'verdict': {
176
+ 'ok': false,
177
+ 'stage': 'acceptance',
178
+ 'report': {
179
+ 'replayedIntents': 2,
180
+ 'candidateIntents': 2,
181
+ 'projection': {'equal': false},
182
+ 'divergences': [
183
+ {
184
+ 'kind': 'plan-divergence',
185
+ 'directiveId': 'registerSite',
186
+ }
187
+ ],
188
+ },
189
+ 'error': 'exact history changes under the candidate',
190
+ }
191
+ }));
192
+ break;
193
+ case 'restorePreviousGeneratedModel':
194
+ _incoming.add(reply({
195
+ 'verdict': {
196
+ 'ok': false,
197
+ 'stage': 'acceptance',
198
+ 'report': {
199
+ 'replayedIntents': 4,
200
+ 'candidateIntents': 3,
201
+ 'projection': {'equal': false},
202
+ 'divergences': [
203
+ {
204
+ 'kind': 'plan-divergence',
205
+ 'directiveId': 'setMood',
206
+ }
207
+ ],
208
+ },
209
+ 'error': 'restoring previous law needs a business reason',
210
+ }
211
+ }));
212
+ break;
213
+ case 'offerDirective':
214
+ _incoming.add(reply({
215
+ 'head': 'HEAD-owned-approval',
216
+ 'created': <Object?>[],
217
+ 'births': <Object?>[],
218
+ 'workspace': 'notes',
219
+ }));
220
+ break;
139
221
  case 'badop':
140
222
  _incoming.add({
141
223
  'v': kWebviewBridgeVersion,
@@ -467,6 +549,32 @@ void main() {
467
549
  expect(bridge.principal, 'user:ada');
468
550
  });
469
551
 
552
+ test(
553
+ 'generated model restoration crosses one framework-private bridge operation',
554
+ () async {
555
+ await bridge.ready;
556
+ final verdict =
557
+ await bridge.nomosFrameworkRestorePreviousGeneratedModel(
558
+ domain: 'evo',
559
+ currentHash: 'current-law-hash',
560
+ because: 'the previous operating policy is required',
561
+ );
562
+
563
+ expect(verdict['ok'], isFalse);
564
+ expect(verdict['stage'], 'acceptance');
565
+ expect(
566
+ ((verdict['report'] as Map)['divergences'] as List).single,
567
+ containsPair('directiveId', 'setMood'));
568
+ final request = runner.sent.firstWhere(
569
+ (message) => message['op'] == 'restorePreviousGeneratedModel',
570
+ );
571
+ expect((request['args'] as Map).cast<String, Object?>(), {
572
+ 'domain': 'evo',
573
+ 'currentHash': 'current-law-hash',
574
+ 'because': 'the previous operating policy is required',
575
+ });
576
+ });
577
+
470
578
  test(
471
579
  'connect / query / queryById / count / sync / lawStatus all wrap the runner',
472
580
  () async {
@@ -481,6 +589,172 @@ void main() {
481
589
  await bridge.lawStatus('abc'), {'installed': true, 'drifted': false});
482
590
  });
483
591
 
592
+ test('moments and historic reads keep commit plumbing behind an opaque handle',
593
+ () async {
594
+ final momentsTransport = ReplayTransport({
595
+ 'commits': [
596
+ {
597
+ 'oid': 'abc123',
598
+ 'at': 1784383200000,
599
+ 'actorSubject': 'user:ada',
600
+ 'domain': 'estate',
601
+ 'directiveId': 'createSite',
602
+ }
603
+ ]
604
+ });
605
+ final momentsBridge = NomosBridge(momentsTransport);
606
+ await momentsBridge.ready;
607
+ final moments = await momentsBridge.moments(limit: 12);
608
+ expect(moments, hasLength(1));
609
+ expect(moments.single.id, 'abc123');
610
+ expect(moments.single.actorSubject, 'user:ada');
611
+ expect(moments.single.action, 'createSite');
612
+ expect((momentsTransport.lastRequest!['args'] as Map)['limit'], 12);
613
+ await momentsBridge.dispose();
614
+
615
+ final readTransport = ReplayTransport({
616
+ 'rows': [
617
+ {
618
+ 'id': 'EstateSite_1',
619
+ 'data': {'name': 'HQ'}
620
+ }
621
+ ]
622
+ });
623
+ final readBridge = NomosBridge(readTransport);
624
+ await readBridge.ready;
625
+ final rows = await readBridge.queryByIdAt(moments.single, 'EstateSite_1');
626
+ expect(rows.single['data'], {'name': 'HQ'});
627
+ expect(readTransport.lastRequest!['op'], 'queryByIdAt');
628
+ expect((readTransport.lastRequest!['args'] as Map),
629
+ containsPair('commit', 'abc123'));
630
+ await readBridge.dispose();
631
+ });
632
+
633
+ test('devices traffic one opaque request and one atomic framework approval',
634
+ () async {
635
+ await bridge.ready;
636
+ final devices = NomosDevices(
637
+ bridge: bridge,
638
+ domain: 'notes',
639
+ domainHash: 'a' * 64,
640
+ actor: 'user:alice',
641
+ scope: 'nomos_internal_owned_read_7c1f5e',
642
+ );
643
+
644
+ final request = await devices.request();
645
+ expect(request, 'opaque-owned-device-request');
646
+ final requestCall = runner.sent.firstWhere(
647
+ (message) => message['op'] == 'cryptoOwnedDeviceRequest',
648
+ );
649
+ expect((requestCall['args'] as Map)['scope'],
650
+ 'nomos_internal_owned_read_7c1f5e');
651
+
652
+ final outcome = await devices.approve(request);
653
+ expect(outcome.head, 'HEAD-owned-approval');
654
+ final prepareCall = runner.sent.firstWhere(
655
+ (message) => message['op'] == 'cryptoOwnedPrepareApproval',
656
+ );
657
+ expect((prepareCall['args'] as Map).cast<String, Object?>(), {
658
+ 'request': 'opaque-owned-device-request',
659
+ 'scope': 'nomos_internal_owned_read_7c1f5e',
660
+ });
661
+ final offerCall = runner.sent.firstWhere(
662
+ (message) => message['op'] == 'offerDirective',
663
+ );
664
+ final offerArgs = (offerCall['args'] as Map).cast<String, Object?>();
665
+ expect(offerArgs['domain'], 'notes');
666
+ expect(offerArgs['directiveId'],
667
+ 'nomos_internal_approve_owned_device_7c1f5e');
668
+ expect(offerArgs['actor'], 'user:alice');
669
+ expect(offerArgs['domainHash'], 'a' * 64);
670
+ expect(offerArgs['payload'], {
671
+ 'newKeySubject': 'key:${'a' * 64}',
672
+ 'ownedCustody': 'opaque-owned-custody',
673
+ 'verifiedVia': 'nomos:test-root',
674
+ 'validFromEpoch': 0,
675
+ 'approvedAt': '2026-07-18T14:20:00.000Z',
676
+ });
677
+ });
678
+
679
+ test('record sharing keeps keys, identity and action payloads behind the bridge',
680
+ () async {
681
+ await bridge.ready;
682
+ final keys = await bridge.nomosOwnedScopeKeys(
683
+ 'owned-reader',
684
+ recordType: 'notes.Note',
685
+ recordId: 'note-1',
686
+ );
687
+ expect(keys.single.epoch, 2);
688
+ expect(keys.single.key, 'opaque-record-key');
689
+
690
+ await bridge.nomosEnsureOwnedIdentity(
691
+ scope: 'owned-reader',
692
+ domain: 'notes',
693
+ domainHash: 'a' * 64,
694
+ );
695
+ final payload = await bridge.nomosPrepareOwnedRecordAction(
696
+ action: 'share',
697
+ scope: 'owned-reader',
698
+ record: const NomosRef(
699
+ workspace: 'ws-1',
700
+ type: 'notes.Note',
701
+ id: 'note-1',
702
+ ),
703
+ peer: 'user:bob',
704
+ );
705
+ expect(payload, containsPair('recordId', 'note-1'));
706
+
707
+ final keyCall = runner.sent.firstWhere(
708
+ (message) => message['op'] == 'cryptoOwnedScopeKeys',
709
+ );
710
+ expect((keyCall['args'] as Map).cast<String, Object?>(), {
711
+ 'scope': 'owned-reader',
712
+ 'record': {'type': 'notes.Note', 'id': 'note-1'},
713
+ });
714
+ final identityCall = runner.sent.firstWhere(
715
+ (message) => message['op'] == 'cryptoOwnedEnsureIdentity',
716
+ );
717
+ expect((identityCall['args'] as Map).cast<String, Object?>(), {
718
+ 'scope': 'owned-reader',
719
+ 'domain': 'notes',
720
+ 'domainHash': 'a' * 64,
721
+ });
722
+ final actionCall = runner.sent.firstWhere(
723
+ (message) => message['op'] == 'cryptoOwnedPrepareRecordAction',
724
+ );
725
+ expect((actionCall['args'] as Map).cast<String, Object?>(), {
726
+ 'action': 'share',
727
+ 'scope': 'owned-reader',
728
+ 'record': {'type': 'notes.Note', 'id': 'note-1'},
729
+ 'peer': 'user:bob',
730
+ });
731
+ });
732
+
733
+ test(
734
+ 'generated model adoption crosses one framework-private bridge operation',
735
+ () async {
736
+ await bridge.ready;
737
+ final verdict = await bridge.nomosFrameworkAdoptGeneratedModel(
738
+ domain: 'evo',
739
+ candidateHash: 'candidate-law-hash',
740
+ because: 'the renamed business action is intentional',
741
+ );
742
+
743
+ expect(verdict['ok'], isFalse);
744
+ expect(verdict['stage'], 'acceptance');
745
+ expect(
746
+ ((verdict['report'] as Map)['divergences'] as List).single,
747
+ containsPair('directiveId', 'registerSite'));
748
+ final request = runner.sent.firstWhere(
749
+ (message) => message['op'] == 'adoptGeneratedModel',
750
+ );
751
+ expect((request['args'] as Map).cast<String, Object?>(), {
752
+ 'domain': 'evo',
753
+ 'candidateHash': 'candidate-law-hash',
754
+ 'because': 'the renamed business action is intentional',
755
+ });
756
+ });
757
+
484
758
  test(
485
759
  'watch is a reactive stream; a runner push reaches the listener; cancel unwatches',
486
760
  () async {
@@ -1,3 +1,9 @@
1
+ ## 0.7.0
2
+
3
+ - Add the public conflict and description shapes used by generated aggregate clients: merge observations,
4
+ pending decisions, typed organisation references and recursive business descriptions now have one Dart
5
+ representation shared with the TypeScript client.
6
+
1
7
  ## 0.5.0
2
8
 
3
9
  - Add typed `SourceSessionNotMounted` error (code `nomos.attested_read.source_not_mounted`) for the colocated attested-read fix — a foreign `read(q, {from})` whose source workspace isn't mounted now fails fast with a typed error + `mountSourceSession` remedy.
@@ -21,9 +21,15 @@ import 'error_info.dart' show NomosBridgeError, NomosErrorInfo;
21
21
  /// recoverable, carrying the eviction/escalation decision fields); everything else stays the generic
22
22
  /// [NomosBridgeError]. Apps `switch` on the type / `code`, never scrape prose.
23
23
  NomosBridgeError nomosErrorFromInfo(NomosErrorInfo info) {
24
- if (info.code.startsWith('nomos.custody.')) return CustodyError.fromInfo(info);
25
- if (info.code.startsWith('nomos.law.')) return LawMismatchError.fromInfo(info);
26
- if (info.code.startsWith('nomos.authz.')) return AuthzRefusedError.fromInfo(info);
24
+ if (info.code == 'nomos.owned.recovery_pending') {
25
+ return OwnedRecoveryPending.fromInfo(info);
26
+ }
27
+ if (info.code.startsWith('nomos.custody.'))
28
+ return CustodyError.fromInfo(info);
29
+ if (info.code.startsWith('nomos.law.'))
30
+ return LawMismatchError.fromInfo(info);
31
+ if (info.code.startsWith('nomos.authz.'))
32
+ return AuthzRefusedError.fromInfo(info);
27
33
  if (info.code.startsWith('nomos.auth.')) return AuthError.fromInfo(info);
28
34
  if (info.code.startsWith('nomos.load.')) return LoadError.fromInfo(info);
29
35
  if (info.code == 'nomos.attested_read.source_not_mounted') {
@@ -52,7 +58,8 @@ class NomosRecommendedAction {
52
58
  static const reauth = 'reauth';
53
59
 
54
60
  /// Quarantine the local snapshot and re-clone fresh (evict local custody).
55
- static const quarantineLocalSnapshotAndReclone = 'quarantineLocalSnapshotAndReclone';
61
+ static const quarantineLocalSnapshotAndReclone =
62
+ 'quarantineLocalSnapshotAndReclone';
56
63
 
57
64
  /// Ask the cloud to serve the full chain and retry (escalate cloud custody).
58
65
  static const refetchFullHistoryAndRetry = 'refetchFullHistoryAndRetry';
@@ -73,6 +80,15 @@ class NomosRecommendedAction {
73
80
  /// [SourceSessionNotMounted] (a directive's `read(q, {from})` resolves in-engine from the mounted source).
74
81
  static const mountSourceSession = 'mountSourceSession';
75
82
 
83
+ /// Ask the designated recovery person to open this workspace once. Nomos completes the handoff privately.
84
+ static const openRecoveryPrincipalDevice = 'openRecoveryPrincipalDevice';
85
+
86
+ /// Wait for the designated recovery person to open this workspace, then reopen this device.
87
+ static const waitForRecoveryPrincipal = 'waitForRecoveryPrincipal';
88
+
89
+ /// Recovery was never enabled: use a working device, or re-birth an empty workspace.
90
+ static const useWorkingDeviceOrRebirth = 'useWorkingDeviceOrRebirth';
91
+
76
92
  /// No automatic remedy — surface to a human.
77
93
  static const none = 'none';
78
94
 
@@ -97,6 +113,12 @@ class NomosRecommendedAction {
97
113
  return 'restore the device signer on the parent (ensureParentSignerEnrollment / NomosScope.home repairs it on reopen)';
98
114
  case mountSourceSession:
99
115
  return 'mount the attested read\'s source workspace as a session on this plane (connect it / ref.acquire) so the read resolves in-engine';
116
+ case openRecoveryPrincipalDevice:
117
+ return 'ask the designated recovery person to open this workspace once; Nomos completes the handoff privately';
118
+ case waitForRecoveryPrincipal:
119
+ return 'wait for the designated recovery person to open this workspace, then reopen this device';
120
+ case useWorkingDeviceOrRebirth:
121
+ return 'use a working device, or re-birth the empty workspace if recovery was never enabled';
100
122
  case none:
101
123
  return 'no automatic remedy — inspect the error';
102
124
  default:
@@ -105,6 +127,56 @@ class NomosRecommendedAction {
105
127
  }
106
128
  }
107
129
 
130
+ /// All-device-loss recovery is a normal, resumable lifecycle state. The app may render the one concrete
131
+ /// [recommendedAction]; it never needs to inspect escrow wraps, Orders, Receipts, or key material.
132
+ class OwnedRecoveryPending extends NomosBridgeError {
133
+ final String code;
134
+ final String? workspace;
135
+ final String stage;
136
+ final String? orderId;
137
+ final String? recoveryPrincipal;
138
+ final String recommendedAction;
139
+
140
+ OwnedRecoveryPending({
141
+ this.code = 'nomos.owned.recovery_pending',
142
+ required String message,
143
+ this.workspace,
144
+ this.stage = 'request',
145
+ this.orderId,
146
+ this.recoveryPrincipal,
147
+ this.recommendedAction = NomosRecommendedAction.waitForRecoveryPrincipal,
148
+ NomosErrorInfo? info,
149
+ }) : super(message, info: info);
150
+
151
+ factory OwnedRecoveryPending.fromInfo(NomosErrorInfo info) {
152
+ String? attr(String name) {
153
+ final value =
154
+ info.attributes['nomos.owned.$name'] ?? info.attributes[name];
155
+ return value?.toString();
156
+ }
157
+
158
+ return OwnedRecoveryPending(
159
+ code: info.code,
160
+ message: info.message,
161
+ workspace: info.workspace,
162
+ stage: attr('stage') ?? 'request',
163
+ orderId: attr('orderId') ?? attr('order_id'),
164
+ recoveryPrincipal:
165
+ attr('recoveryPrincipal') ?? attr('recovery_principal'),
166
+ recommendedAction: attr('recommendedAction') ??
167
+ attr('recommended_action') ??
168
+ NomosRecommendedAction.waitForRecoveryPrincipal,
169
+ info: info,
170
+ );
171
+ }
172
+
173
+ @override
174
+ String toString() =>
175
+ 'OwnedRecoveryPending(stage: $stage${orderId != null ? ', order: $orderId' : ''}'
176
+ '${recoveryPrincipal != null ? ', recoveryPrincipal: $recoveryPrincipal' : ''}, '
177
+ 'recommendedAction: $recommendedAction): $message';
178
+ }
179
+
108
180
  /// LAW DRIFT — the client was generated under one law and the workspace runs another (`nomos.law.*`): either a
109
181
  /// §5 connect-time compat check, or a dispatch-time refusal when a payload's baked hash names law the workspace
110
182
  /// no longer runs. When [structurallyCompatible] is true the operation can still proceed (structural subtyping);
@@ -169,12 +241,18 @@ class LawMismatchError extends NomosBridgeError {
169
241
  code: info.code,
170
242
  message: info.message,
171
243
  workspace: info.workspace,
172
- generatedFor: attr('generatedFor') ?? attr('generated_for') ?? attr('clientHash'),
173
- workspaceRuns: attr('workspaceRuns') ?? attr('workspace_runs') ?? attr('installed'),
244
+ generatedFor:
245
+ attr('generatedFor') ?? attr('generated_for') ?? attr('clientHash'),
246
+ workspaceRuns:
247
+ attr('workspaceRuns') ?? attr('workspace_runs') ?? attr('installed'),
174
248
  operation: info.operation ?? attr('operation'),
175
249
  structurallyCompatible: compat(),
176
- incompatibleMember: attr('incompatibleMember') ?? attr('incompatible_member') ?? attr('member'),
177
- recommendedAction: attr('recommendedAction') ?? attr('recommended_action') ?? NomosRecommendedAction.recompileClient,
250
+ incompatibleMember: attr('incompatibleMember') ??
251
+ attr('incompatible_member') ??
252
+ attr('member'),
253
+ recommendedAction: attr('recommendedAction') ??
254
+ attr('recommended_action') ??
255
+ NomosRecommendedAction.recompileClient,
178
256
  info: info,
179
257
  );
180
258
  }
@@ -261,9 +339,11 @@ class AuthzRefusedError extends NomosBridgeError {
261
339
  keyHash: attr('keyHash') ?? attr('key_hash'),
262
340
  object: attr('object'),
263
341
  relation: attr('relation'),
264
- directiveId: attr('directiveId') ?? attr('directive_id') ?? attr('directive'),
342
+ directiveId:
343
+ attr('directiveId') ?? attr('directive_id') ?? attr('directive'),
265
344
  );
266
- if (info.code == 'nomos.authz.signer_enrollment_missing' || cause == 'missing-signer-enrollment') {
345
+ if (info.code == 'nomos.authz.signer_enrollment_missing' ||
346
+ cause == 'missing-signer-enrollment') {
267
347
  return ParentSignerEnrollmentMissing(
268
348
  message: info.message,
269
349
  workspace: fields.workspace,
@@ -284,7 +364,9 @@ class AuthzRefusedError extends NomosBridgeError {
284
364
  relation: fields.relation,
285
365
  directiveId: fields.directiveId,
286
366
  cause: cause,
287
- recommendedAction: attr('recommendedAction') ?? attr('recommended_action') ?? NomosRecommendedAction.none,
367
+ recommendedAction: attr('recommendedAction') ??
368
+ attr('recommended_action') ??
369
+ NomosRecommendedAction.none,
288
370
  info: info,
289
371
  );
290
372
  }
@@ -321,7 +403,8 @@ class ParentSignerEnrollmentMissing extends AuthzRefusedError {
321
403
  relation: 'signer',
322
404
  directiveId: directiveId,
323
405
  cause: 'missing-signer-enrollment',
324
- recommendedAction: NomosRecommendedAction.ensureParentSignerEnrollment,
406
+ recommendedAction:
407
+ NomosRecommendedAction.ensureParentSignerEnrollment,
325
408
  info: info,
326
409
  );
327
410
  }
@@ -367,7 +450,9 @@ class AuthError extends NomosBridgeError {
367
450
  workspace: info.workspace,
368
451
  issuer: attr('issuer'),
369
452
  operation: info.operation ?? attr('operation'),
370
- recommendedAction: attr('recommendedAction') ?? attr('recommended_action') ?? NomosRecommendedAction.reauth,
453
+ recommendedAction: attr('recommendedAction') ??
454
+ attr('recommended_action') ??
455
+ NomosRecommendedAction.reauth,
371
456
  info: info,
372
457
  );
373
458
  }
@@ -415,7 +500,9 @@ class LoadError extends NomosBridgeError {
415
500
  message: info.message,
416
501
  artifact: attr('artifact'),
417
502
  sourceUrl: attr('sourceUrl') ?? attr('source_url') ?? attr('url'),
418
- recommendedAction: attr('recommendedAction') ?? attr('recommended_action') ?? NomosRecommendedAction.retry,
503
+ recommendedAction: attr('recommendedAction') ??
504
+ attr('recommended_action') ??
505
+ NomosRecommendedAction.retry,
419
506
  info: info,
420
507
  );
421
508
  }
@@ -462,7 +549,8 @@ class SourceSessionNotMounted extends NomosBridgeError {
462
549
 
463
550
  factory SourceSessionNotMounted.fromInfo(NomosErrorInfo info) {
464
551
  String? attr(String name) {
465
- final v = info.attributes['nomos.attested_read.$name'] ?? info.attributes[name];
552
+ final v =
553
+ info.attributes['nomos.attested_read.$name'] ?? info.attributes[name];
466
554
  return v?.toString();
467
555
  }
468
556
 
@@ -470,16 +558,19 @@ class SourceSessionNotMounted extends NomosBridgeError {
470
558
  code: info.code,
471
559
  message: info.message,
472
560
  from: attr('from'),
473
- sourceWorkspace: attr('source_workspace') ?? attr('sourceWorkspace') ?? info.workspace,
474
- queryId: (info.attributes['nomos.query_id'] ?? attr('queryId'))?.toString(),
475
- recommendedAction: attr('recommendedAction') ?? attr('recommended_action') ?? NomosRecommendedAction.mountSourceSession,
561
+ sourceWorkspace:
562
+ attr('source_workspace') ?? attr('sourceWorkspace') ?? info.workspace,
563
+ queryId:
564
+ (info.attributes['nomos.query_id'] ?? attr('queryId'))?.toString(),
565
+ recommendedAction: attr('recommendedAction') ??
566
+ attr('recommended_action') ??
567
+ NomosRecommendedAction.mountSourceSession,
476
568
  info: info,
477
569
  );
478
570
  }
479
571
 
480
572
  @override
481
- String toString() =>
482
- 'SourceSessionNotMounted(source: $sourceWorkspace'
573
+ String toString() => 'SourceSessionNotMounted(source: $sourceWorkspace'
483
574
  '${queryId != null ? ', query: $queryId' : ''}, recommendedAction: $recommendedAction): $message';
484
575
  }
485
576
 
@@ -587,13 +678,16 @@ class CustodyError extends NomosBridgeError {
587
678
  orElse: () => CustodySource.unknown,
588
679
  ),
589
680
  recoverable: attr('recoverable') != 'false',
590
- recommendedAction: attr('recommended_action') ?? attr('recommendedAction') ?? 'refetchFullHistoryAndRetry',
681
+ recommendedAction: attr('recommended_action') ??
682
+ attr('recommendedAction') ??
683
+ 'refetchFullHistoryAndRetry',
591
684
  info: info,
592
685
  );
593
686
  }
594
687
 
595
688
  /// True when the remedy is to quarantine the local snapshot and re-clone (evict local custody).
596
- bool get shouldEvictLocalCustody => recommendedAction == 'quarantineLocalSnapshotAndReclone';
689
+ bool get shouldEvictLocalCustody =>
690
+ recommendedAction == 'quarantineLocalSnapshotAndReclone';
597
691
 
598
692
  @override
599
693
  String toString() =>
@@ -607,7 +701,8 @@ class CustodyError extends NomosBridgeError {
607
701
  /// REMEDY: re-acquire a fresh token from the auth provider and reconnect.
608
702
  class AuthTokenInvalid extends NomosBridgeError {
609
703
  AuthTokenInvalid([String? message])
610
- : super(message ?? 'the auth-provider token was missing or rejected (the cloud refused 401) — a birth needs a verified principal');
704
+ : super(message ??
705
+ 'the auth-provider token was missing or rejected (the cloud refused 401) — a birth needs a verified principal');
611
706
  }
612
707
 
613
708
  /// The workspace name the cloud reported for the born home does NOT equal `homeNameOf(subject)`. The home's
@@ -619,8 +714,10 @@ class HomeNameMismatch extends NomosBridgeError {
619
714
 
620
715
  /// What the cloud reported (its status `lineage.id` / the name it served).
621
716
  final String reported;
622
- HomeNameMismatch({required this.expected, required this.reported, String? message})
623
- : super(message ?? 'the cloud reported home "$reported" but this subject hashes to "$expected" — refusing to mount a home whose name does not bind its owner');
717
+ HomeNameMismatch(
718
+ {required this.expected, required this.reported, String? message})
719
+ : super(message ??
720
+ 'the cloud reported home "$reported" but this subject hashes to "$expected" — refusing to mount a home whose name does not bind its owner');
624
721
  }
625
722
 
626
723
  /// The workspace exists and is born, but the HOME LAW is not installed/active on it — so it is a plain
@@ -631,7 +728,8 @@ class HomeLawMissing extends NomosBridgeError {
631
728
  /// The workspace that is missing the home law.
632
729
  final String workspace;
633
730
  HomeLawMissing({required this.workspace, String? message})
634
- : super(message ?? 'workspace "$workspace" is born but the home law is not installed/active on it — refusing to mount a plain workspace as a home');
731
+ : super(message ??
732
+ 'workspace "$workspace" is born but the home law is not installed/active on it — refusing to mount a plain workspace as a home');
635
733
  }
636
734
 
637
735
  /// The offer to the home's birth lane was REFUSED by the parent's gate (the law denied the birth — e.g. the
@@ -644,14 +742,17 @@ class BirthRejected extends NomosBridgeError {
644
742
  /// The HTTP status the cloud answered, when known.
645
743
  final int? statusCode;
646
744
  BirthRejected({String? message, this.directiveId, this.statusCode})
647
- : super(message ?? 'the home birth offer was refused by the law${directiveId != null ? ' (directive "$directiveId")' : ''}${statusCode != null ? ' [$statusCode]' : ''}');
745
+ : super(message ??
746
+ 'the home birth offer was refused by the law${directiveId != null ? ' (directive "$directiveId")' : ''}${statusCode != null ? ' [$statusCode]' : ''}');
648
747
  }
649
748
 
650
749
  /// A pack the cloud returned (or the local holon produced) was malformed — it could not be parsed/applied as
651
750
  /// a git pack. This is a transport/custody integrity fault, not a law refusal. REMEDY: retry; if persistent,
652
751
  /// the custody bytes are corrupt.
653
752
  class PackMalformed extends NomosBridgeError {
654
- PackMalformed([String? message]) : super(message ?? 'a returned pack was malformed and could not be applied (custody integrity fault)');
753
+ PackMalformed([String? message])
754
+ : super(message ??
755
+ 'a returned pack was malformed and could not be applied (custody integrity fault)');
655
756
  }
656
757
 
657
758
  /// The home law's content hash could not be resolved — the cloud reported no current law for the home's
@@ -661,7 +762,8 @@ class LawHashUnavailable extends NomosBridgeError {
661
762
  /// The workspace whose current law hash could not be resolved.
662
763
  final String workspace;
663
764
  LawHashUnavailable({required this.workspace, String? message})
664
- : super(message ?? 'could not resolve the current law hash for "$workspace" — the cloud reported no installed law for the home');
765
+ : super(message ??
766
+ 'could not resolve the current law hash for "$workspace" — the cloud reported no installed law for the home');
665
767
  }
666
768
 
667
769
  /// AUTHORITY IS UNFORGEABLE-BY-CONSTRUCTION: a GATED directive (one carrying a `.requires(relation)` write
@@ -678,7 +780,11 @@ class MissingOfferAuthority extends NomosBridgeError {
678
780
 
679
781
  /// The relation the law requires the author to hold (e.g. `admin`).
680
782
  final String requires;
681
- MissingOfferAuthority({required this.domain, required this.directiveId, required this.requires, String? message})
783
+ MissingOfferAuthority(
784
+ {required this.domain,
785
+ required this.directiveId,
786
+ required this.requires,
787
+ String? message})
682
788
  : super(message ??
683
789
  '$domain/$directiveId requires the "$requires" relation — construct the client with an actor (the verified principal) to author it; a gated offer cannot be made without an authority');
684
790
  }