@githolon/dsl 0.100.6 → 0.101.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dart/CHANGELOG.md +6 -0
- package/dart/lib/src/dev_cloud.dart +46 -15
- package/dart/lib/src/test_harness.dart +78 -16
- package/dart/lib/src/webview_bridge.dart +640 -126
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/bundle_version_pairing_test.dart +11 -22
- package/dart/test/co2_product_gauntlet_test.dart +228 -0
- package/dart/test/errors_test.dart +63 -12
- package/dart/test/first_app_gauntlet_test.dart +29 -3
- package/dart/test/webview_bridge_test.dart +274 -0
- package/dart_types/CHANGELOG.md +6 -0
- package/dart_types/lib/src/errors.dart +136 -30
- package/dart_types/lib/src/surfaced.dart +48 -2
- package/dart_types/lib/src/wire.dart +3 -1
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +19 -0
- package/package.json +1 -1
- package/src/activity.ts +43 -4
- package/src/aggregate.ts +132 -22
- package/src/attested_core.ts +1 -1
- package/src/authoring.ts +119 -6
- package/src/birth_meter.ts +4 -4
- package/src/birth_meter_ensure.ts +9 -1
- package/src/births.ts +53 -22
- package/src/build_package.ts +55 -14
- package/src/business_birth.ts +48 -7
- package/src/codegen_dart.ts +267 -37
- package/src/codegen_dart_client.ts +921 -82
- package/src/codegen_openapi.ts +28 -5
- package/src/codegen_proof.ts +107 -11
- package/src/codegen_ts.ts +961 -135
- package/src/compile_package_main.ts +155 -45
- package/src/conflicts.ts +252 -0
- package/src/describe_model.ts +448 -0
- package/src/directive.ts +5 -38
- package/src/drivers.ts +12 -4
- package/src/engine_entry.ts +4 -0
- package/src/evolve_preview.ts +72 -11
- package/src/fields.ts +201 -55
- package/src/framework/application_home.ts +103 -0
- package/src/framework/application_home_domain.ts +2 -0
- package/src/framework/attested_receipts.ts +2 -2
- package/src/framework/auth_provider_law.ts +51 -24
- package/src/framework/business_corrections.ts +170 -0
- package/src/framework/disclosure.ts +1 -1
- package/src/framework/domain_lifecycle.ts +250 -16
- package/src/framework/encryption_identity.ts +1 -1
- package/src/framework/encryption_scope.ts +21 -13
- package/src/framework/era_certification.ts +1 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +1 -1
- package/src/framework/home.ts +22 -9
- package/src/framework/identity.ts +5 -5
- package/src/framework/impure_capability.ts +2 -2
- package/src/framework/owned_custody.ts +561 -0
- package/src/framework/platform_admin.ts +1 -1
- package/src/framework/relations.ts +9 -1
- package/src/framework/repair.ts +1 -1
- package/src/framework/signing_identity.ts +2 -2
- package/src/framework/source_roster.ts +1 -1
- package/src/framework/workspaces.ts +17 -10
- package/src/generated_client_names.ts +39 -0
- package/src/index.ts +20 -15
- package/src/kernel_capabilities.ts +25 -1
- package/src/law_check.ts +4 -1
- package/src/manifest.ts +356 -63
- package/src/read_projection_manifest.ts +8 -0
- package/src/read_projection_namespace.ts +4 -0
- package/src/relation.ts +1 -1
- package/src/shape_compat.ts +47 -2
- package/src/stable_ids.ts +38 -6
- package/src/stable_ids_types.ts +30 -0
- package/src/surfaced_lint.ts +0 -0
- package/src/usd.ts +62 -11
- package/src/usd_layers.ts +54 -3
- package/src/value_object.ts +23 -19
- package/src/wire.ts +1 -0
- package/src/wire_encode.ts +48 -4
- package/src/workspace_routing.ts +57 -0
- package/src/workspace_sharding.ts +9 -9
- 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 {
|
package/dart_types/CHANGELOG.md
CHANGED
|
@@ -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
|
|
25
|
-
|
|
26
|
-
|
|
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 =
|
|
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:
|
|
173
|
-
|
|
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') ??
|
|
177
|
-
|
|
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:
|
|
342
|
+
directiveId:
|
|
343
|
+
attr('directiveId') ?? attr('directive_id') ?? attr('directive'),
|
|
265
344
|
);
|
|
266
|
-
if (info.code == 'nomos.authz.signer_enrollment_missing' ||
|
|
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') ??
|
|
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:
|
|
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') ??
|
|
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') ??
|
|
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 =
|
|
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:
|
|
474
|
-
|
|
475
|
-
|
|
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') ??
|
|
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 =>
|
|
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 ??
|
|
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(
|
|
623
|
-
|
|
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 ??
|
|
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 ??
|
|
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])
|
|
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 ??
|
|
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(
|
|
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
|
}
|