@githolon/dsl 0.58.1 → 0.59.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 CHANGED
@@ -1,3 +1,6 @@
1
+ ## 0.38.0
2
+ - SPLIT: the pure data core (wire types, offer codec, NomosReads, aggregate meta, schema validation, error info) moved to the new `nomos_types` package; `nomos_client` depends on and re-exports it, so existing imports are unchanged. Frontend/UI code can now depend on `nomos_types` alone.
3
+
1
4
  ## 0.37.1
2
5
 
3
6
  - Doc fix: `NomosBridge.attestedRead` docs now match the runtime — the call answers `{ entry: {queryId, result, attestation: …} }`; pass `result['entry']` as one `offerDirective` `attestations` element.
@@ -23,25 +23,17 @@
23
23
  /// which re-exports this framework barrel PLUS every generated domain.
24
24
  library;
25
25
 
26
- export 'src/wire.dart';
27
- export 'src/driver.dart';
28
- export 'src/offer.dart';
29
- // THE TRI-STATE SLOT — NomosSlot<T> (Omitted / ExplicitNull / Present) for `nullable`/`clearable`
30
- // declared fields (architecture/null_semantics.md); generated code references it, never redefines it.
31
- export 'src/null_slot.dart';
26
+ // THE PURE-DATA CORE — the hand-written kernel wire types, the `offer(intent)` codec,
27
+ // the tri-state `NomosSlot<T>`, `NomosReads`, schema validation, runtime aggregate
28
+ // metadata, the data-only error/trace types, and the typed diagnostics moved to the
29
+ // standalone `nomos_types` package (so generated per-domain TYPES files resolve against
30
+ // it alone). Re-exported here so this barrel's public API is unchanged for consumers.
31
+ export 'package:nomos_types/nomos_types.dart';
32
32
  // homeNameOf(subject) — the canonical home-workspace name (parity with @githolon/client + the law side).
33
33
  export 'src/home.dart';
34
- export 'src/subscriptions.dart';
35
- export 'src/schema_validation.dart';
36
- // RUNTIME AGGREGATE METADATA — the generated per-domain registry's support types
37
- // (lookup by aggregateType for generic surfaces: history UIs, op renderers, agents).
38
- export 'src/aggregate_meta.dart';
39
34
  // THE WEBVIEW BRIDGE (app-binding boundary) — the async Dart runtime that drives the REAL githolon running
40
35
  // in a WebView, so the generated typed client never needs a `MemoryPersistence` fake.
41
36
  export 'src/webview_bridge.dart';
42
- // TYPED DIAGNOSTICS (co2 feedback req 7) — the fixed set of ways the home-birth ceremony can fail, each
43
- // extending NomosBridgeError so existing handlers still catch them, named so the app can branch on `is`.
44
- export 'src/errors.dart';
45
37
  // The PROVIDER boundary (server-side capability-provider SDK runtime: TaskOutcome /
46
38
  // TaskSuccess / TaskFailure / TaskResult / TaskProvider / HostClock). The symmetric twin
47
39
  // of `offer.dart`; the generated per-task provider classes build on it.
@@ -31,7 +31,7 @@ library;
31
31
 
32
32
  import 'dart:convert';
33
33
 
34
- import 'offer.dart' show NomosIntent;
34
+ import 'package:nomos_types/nomos_types.dart' show NomosIntent;
35
35
 
36
36
  /// The typed OUTCOME a provider handler returns for one Obligation: success, failure, blocked,
37
37
  /// or dead-lettered.
@@ -18,7 +18,8 @@ import 'dart:async';
18
18
  import 'dart:convert';
19
19
  import 'dart:math';
20
20
 
21
- import 'errors.dart' show nomosErrorFromInfo;
21
+ import 'package:nomos_types/nomos_types.dart'
22
+ show nomosErrorFromInfo, NomosTraceContext, NomosErrorInfo;
22
23
 
23
24
  /// The protocol version the runner and the bridge agree on (`webview-runner.mjs` `WEBVIEW_BRIDGE_VERSION`).
24
25
  const int kWebviewBridgeVersion = 1;
@@ -31,180 +32,6 @@ abstract interface class BridgeTransport {
31
32
  Stream<Map<String, Object?>> get messages;
32
33
  }
33
34
 
34
- /// Trace/span context carried over the bridge as plain JSON. It is intentionally OpenTelemetry-shaped without
35
- /// importing an OTel SDK: apps can map it to spans later, while Nomos stays dependency-light at the runtime edge.
36
- class NomosTraceContext {
37
- final String? traceId;
38
- final String? spanId;
39
- final String? parentSpanId;
40
- final bool? sampled;
41
-
42
- const NomosTraceContext({
43
- this.traceId,
44
- this.spanId,
45
- this.parentSpanId,
46
- this.sampled,
47
- });
48
-
49
- factory NomosTraceContext.fromJson(Object? value) {
50
- if (value is! Map) return const NomosTraceContext();
51
- final m = value.cast<String, Object?>();
52
- return NomosTraceContext(
53
- traceId: m['traceId']?.toString(),
54
- spanId: m['spanId']?.toString(),
55
- parentSpanId: m['parentSpanId']?.toString(),
56
- sampled: m['sampled'] is bool ? m['sampled'] as bool : null,
57
- );
58
- }
59
-
60
- Map<String, Object?> toJson() => {
61
- if (traceId != null) 'traceId': traceId,
62
- if (spanId != null) 'spanId': spanId,
63
- if (parentSpanId != null) 'parentSpanId': parentSpanId,
64
- if (sampled != null) 'sampled': sampled,
65
- };
66
-
67
- bool get isEmpty =>
68
- traceId == null &&
69
- spanId == null &&
70
- parentSpanId == null &&
71
- sampled == null;
72
- }
73
-
74
- /// Structured runtime error detail for the bridge boundary. Keep this data-only: it is safe to serialize, log,
75
- /// persist, or later attach to an OpenTelemetry span without importing tracing into the core client package.
76
- class NomosErrorInfo {
77
- final String code;
78
- final String message;
79
- final String component;
80
- final String? operation;
81
- final String? workspace;
82
- final String? domain;
83
- final String? directiveId;
84
- final NomosTraceContext trace;
85
- final Map<String, Object?> attributes;
86
- final NomosErrorInfo? cause;
87
-
88
- const NomosErrorInfo({
89
- required this.code,
90
- required this.message,
91
- this.component = 'nomos.client',
92
- this.operation,
93
- this.workspace,
94
- this.domain,
95
- this.directiveId,
96
- this.trace = const NomosTraceContext(),
97
- this.attributes = const {},
98
- this.cause,
99
- });
100
-
101
- factory NomosErrorInfo.legacy(
102
- Object? message, {
103
- String code = 'nomos.bridge.error',
104
- String component = 'nomos.client',
105
- String? operation,
106
- NomosTraceContext trace = const NomosTraceContext(),
107
- }) {
108
- return NomosErrorInfo(
109
- code: code,
110
- message: (message ?? 'the githolon refused the request').toString(),
111
- component: component,
112
- operation: operation,
113
- trace: trace,
114
- );
115
- }
116
-
117
- factory NomosErrorInfo.fromJson(
118
- Object? value, {
119
- Object? fallbackMessage,
120
- String? fallbackOperation,
121
- NomosTraceContext fallbackTrace = const NomosTraceContext(),
122
- }) {
123
- if (value is! Map) {
124
- return NomosErrorInfo.legacy(
125
- fallbackMessage,
126
- operation: fallbackOperation,
127
- trace: fallbackTrace,
128
- );
129
- }
130
- final m = value.cast<String, Object?>();
131
- final trace = NomosTraceContext.fromJson(m['trace']);
132
- return NomosErrorInfo(
133
- code: (m['code'] ?? 'nomos.bridge.error').toString(),
134
- message: (m['message'] ??
135
- fallbackMessage ??
136
- 'the githolon refused the request')
137
- .toString(),
138
- component: (m['component'] ?? 'nomos.client').toString(),
139
- operation: m['operation']?.toString() ?? fallbackOperation,
140
- workspace: m['workspace']?.toString(),
141
- domain: m['domain']?.toString(),
142
- directiveId: m['directiveId']?.toString(),
143
- trace: trace.isEmpty ? fallbackTrace : trace,
144
- attributes:
145
- ((m['attributes'] as Map?)?.cast<String, Object?>()) ?? const {},
146
- cause: m['cause'] == null
147
- ? null
148
- : NomosErrorInfo.fromJson(m['cause'],
149
- fallbackOperation: fallbackOperation),
150
- );
151
- }
152
-
153
- factory NomosErrorInfo.fromBridgeMessage(
154
- Map<String, Object?> message, {
155
- String? fallbackOperation,
156
- NomosTraceContext fallbackTrace = const NomosTraceContext(),
157
- Object? fallbackMessage,
158
- }) {
159
- return NomosErrorInfo.fromJson(
160
- message['errorInfo'],
161
- fallbackMessage: message['error'] ?? fallbackMessage,
162
- fallbackOperation: fallbackOperation,
163
- fallbackTrace: fallbackTrace,
164
- );
165
- }
166
-
167
- Map<String, Object?> toJson() => {
168
- 'code': code,
169
- 'message': message,
170
- 'component': component,
171
- if (operation != null) 'operation': operation,
172
- if (workspace != null) 'workspace': workspace,
173
- if (domain != null) 'domain': domain,
174
- if (directiveId != null) 'directiveId': directiveId,
175
- if (!trace.isEmpty) 'trace': trace.toJson(),
176
- if (attributes.isNotEmpty) 'attributes': attributes,
177
- if (cause != null) 'cause': cause!.toJson(),
178
- };
179
-
180
- /// Attribute map suitable for attaching to a future OTel span/event. The keys are stable and low-cardinality
181
- /// where possible; high-cardinality values stay explicit (`workspace`, `domain`, directive) so apps can redact.
182
- Map<String, Object?> toTelemetryAttributes() => {
183
- 'exception.type': code,
184
- 'exception.message': message,
185
- 'nomos.error.code': code,
186
- 'nomos.component': component,
187
- if (operation != null) 'nomos.operation': operation,
188
- if (workspace != null) 'nomos.workspace': workspace,
189
- if (domain != null) 'nomos.domain': domain,
190
- if (directiveId != null) 'nomos.directive_id': directiveId,
191
- if (trace.traceId != null) 'trace_id': trace.traceId,
192
- if (trace.spanId != null) 'span_id': trace.spanId,
193
- ...attributes,
194
- };
195
- }
196
-
197
- /// A typed refusal surfaced from the githolon's gate (a law refusal, a drift, a malformed op). Carries the
198
- /// human-first message verbatim plus structured detail for logging, UI mapping, and future OTel export.
199
- class NomosBridgeError implements Exception {
200
- final String message;
201
- final NomosErrorInfo info;
202
- NomosBridgeError(this.message, {NomosErrorInfo? info})
203
- : info = info ?? NomosErrorInfo.legacy(message);
204
- @override
205
- String toString() => 'NomosBridgeError: $message';
206
- }
207
-
208
35
  /// Live byte-progress of a custody pack transfer (the big initial clone / heavy resync over `/pack`).
209
36
  /// [total] is the host's REAL packed size from `Content-Length` (known before the first body byte — the
210
37
  /// warm host packs into memory then sends), so [fraction] is a truthful 0→1 bar, never an estimate.
@@ -0,0 +1,13 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth.
3
+ //
4
+ /// The TEST-ONLY entrypoint (`package:nomos_client/testing.dart`) — the headless harness
5
+ /// (NomosTestHarness drives the REAL githolon under `dart test`, no WebView) and the
6
+ /// node-runner transport it rides. Depend on this ONLY from `dev_dependencies`-scoped
7
+ /// packages: it pulls `dart:io` and process management, which never belong in app code.
8
+ /// The layered-packages emit (`dart.packages.test`) re-exports this as the generated
9
+ /// test package's harness surface.
10
+ library;
11
+
12
+ export 'src/node_runner_transport.dart';
13
+ export 'src/test_harness.dart';
package/dart/pubspec.lock CHANGED
@@ -185,6 +185,13 @@ packages:
185
185
  url: "https://pub.dev"
186
186
  source: hosted
187
187
  version: "2.0.2"
188
+ nomos_types:
189
+ dependency: "direct main"
190
+ description:
191
+ path: "../dart_types"
192
+ relative: true
193
+ source: path
194
+ version: "0.1.0"
188
195
  package_config:
189
196
  dependency: transitive
190
197
  description:
package/dart/pubspec.yaml CHANGED
@@ -11,12 +11,16 @@ description: >-
11
11
  Nomos 2 Dart frontend types — the hand-written kernel wire types plus the
12
12
  code-generated per-domain aggregate + directive-payload types. (De)serializes
13
13
  the EXACT serde JSON the Rust kernel + projection produce/consume.
14
- version: 0.37.1
14
+ version: 0.38.0
15
15
 
16
16
  environment:
17
17
  sdk: ^3.5.0
18
18
 
19
19
  dependencies:
20
+ # The pure-data core (wire types, offer codec, NomosReads, schema validation, error
21
+ # info). nomos_client depends on and re-exports it, so existing imports are unchanged.
22
+ # For local monorepo dev, pubspec_overrides.yaml points this at ../dart_types.
23
+ nomos_types: ^0.1.0
20
24
  json_schema: ^5.2.2
21
25
  crypto: ^3.0.0
22
26
 
@@ -0,0 +1,7 @@
1
+ # IN-REPO RESOLUTION: nomos_types lives in this monorepo (dsl/dart_types) and is not yet on
2
+ # pub.dev — this override makes `dart pub get` resolve it from source for everyone cloning the
3
+ # repo. Once nomos_types is published, consumers outside the repo resolve the hosted ^0.1.0;
4
+ # this file only affects in-repo development.
5
+ dependency_overrides:
6
+ nomos_types:
7
+ path: ../dart_types
@@ -0,0 +1,6 @@
1
+ ## 0.1.0
2
+ - Initial release. The pure-data core split out of `nomos_client`: kernel wire types,
3
+ the offer codec, `NomosReads`, the tri-state `NomosSlot`, aggregate metadata, schema
4
+ validation, and the data-only error/trace types (`NomosBridgeError`, `NomosErrorInfo`,
5
+ `NomosTraceContext`) plus the typed diagnostics. No transport, no bridge, no runtime —
6
+ generated per-domain TYPES files depend on only this.
@@ -0,0 +1,29 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded QuickJS engine}.
4
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
5
+
6
+ /// Nomos 2 pure-data core — the hand-written kernel WIRE/data types with NO transport,
7
+ /// NO bridge, NO runtime, NO Flutter. Generated per-domain TYPES files depend on ONLY
8
+ /// this, so they resolve without pulling in sessions/workspace lifecycle. The
9
+ /// `nomos_client` package depends on and re-exports this barrel, so existing
10
+ /// `package:nomos_client/nomos_client.dart` imports are unchanged.
11
+ ///
12
+ /// - The kernel WIRE types (`Value`, `Field`/`WireField`, `Hlc`, `Op`, `FieldOp`,
13
+ /// `Event`, `Schema`/`WireDriver`) — byte-compatible with serde_json.
14
+ /// - The ONE effectful write verb `offer(intent)` + the typed reactive read engine
15
+ /// `NomosReads`.
16
+ /// - The tri-state `NomosSlot<T>`, runtime aggregate metadata, schema validation, and
17
+ /// the data-only error/trace types (`NomosBridgeError`, `NomosErrorInfo`,
18
+ /// `NomosTraceContext`) plus the typed diagnostics.
19
+ library;
20
+
21
+ export 'src/wire.dart';
22
+ export 'src/driver.dart';
23
+ export 'src/offer.dart';
24
+ export 'src/null_slot.dart';
25
+ export 'src/subscriptions.dart';
26
+ export 'src/schema_validation.dart';
27
+ export 'src/aggregate_meta.dart';
28
+ export 'src/error_info.dart';
29
+ export 'src/errors.dart';
@@ -0,0 +1,184 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded QuickJS engine}.
4
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
5
+
6
+ /// Data-only error/trace types for the bridge boundary. These carry NO transport, NO
7
+ /// bridge, NO runtime — they are pure serializable value types, so they live in
8
+ /// `nomos_types` and are safe to depend on from generated types files + UI code. The
9
+ /// async [NomosBridge] runtime (in `nomos_client`'s `webview_bridge.dart`) builds on them.
10
+ library;
11
+
12
+ /// Trace/span context carried over the bridge as plain JSON. It is intentionally OpenTelemetry-shaped without
13
+ /// importing an OTel SDK: apps can map it to spans later, while Nomos stays dependency-light at the runtime edge.
14
+ class NomosTraceContext {
15
+ final String? traceId;
16
+ final String? spanId;
17
+ final String? parentSpanId;
18
+ final bool? sampled;
19
+
20
+ const NomosTraceContext({
21
+ this.traceId,
22
+ this.spanId,
23
+ this.parentSpanId,
24
+ this.sampled,
25
+ });
26
+
27
+ factory NomosTraceContext.fromJson(Object? value) {
28
+ if (value is! Map) return const NomosTraceContext();
29
+ final m = value.cast<String, Object?>();
30
+ return NomosTraceContext(
31
+ traceId: m['traceId']?.toString(),
32
+ spanId: m['spanId']?.toString(),
33
+ parentSpanId: m['parentSpanId']?.toString(),
34
+ sampled: m['sampled'] is bool ? m['sampled'] as bool : null,
35
+ );
36
+ }
37
+
38
+ Map<String, Object?> toJson() => {
39
+ if (traceId != null) 'traceId': traceId,
40
+ if (spanId != null) 'spanId': spanId,
41
+ if (parentSpanId != null) 'parentSpanId': parentSpanId,
42
+ if (sampled != null) 'sampled': sampled,
43
+ };
44
+
45
+ bool get isEmpty =>
46
+ traceId == null &&
47
+ spanId == null &&
48
+ parentSpanId == null &&
49
+ sampled == null;
50
+ }
51
+
52
+ /// Structured runtime error detail for the bridge boundary. Keep this data-only: it is safe to serialize, log,
53
+ /// persist, or later attach to an OpenTelemetry span without importing tracing into the core client package.
54
+ class NomosErrorInfo {
55
+ final String code;
56
+ final String message;
57
+ final String component;
58
+ final String? operation;
59
+ final String? workspace;
60
+ final String? domain;
61
+ final String? directiveId;
62
+ final NomosTraceContext trace;
63
+ final Map<String, Object?> attributes;
64
+ final NomosErrorInfo? cause;
65
+
66
+ const NomosErrorInfo({
67
+ required this.code,
68
+ required this.message,
69
+ this.component = 'nomos.client',
70
+ this.operation,
71
+ this.workspace,
72
+ this.domain,
73
+ this.directiveId,
74
+ this.trace = const NomosTraceContext(),
75
+ this.attributes = const {},
76
+ this.cause,
77
+ });
78
+
79
+ factory NomosErrorInfo.legacy(
80
+ Object? message, {
81
+ String code = 'nomos.bridge.error',
82
+ String component = 'nomos.client',
83
+ String? operation,
84
+ NomosTraceContext trace = const NomosTraceContext(),
85
+ }) {
86
+ return NomosErrorInfo(
87
+ code: code,
88
+ message: (message ?? 'the githolon refused the request').toString(),
89
+ component: component,
90
+ operation: operation,
91
+ trace: trace,
92
+ );
93
+ }
94
+
95
+ factory NomosErrorInfo.fromJson(
96
+ Object? value, {
97
+ Object? fallbackMessage,
98
+ String? fallbackOperation,
99
+ NomosTraceContext fallbackTrace = const NomosTraceContext(),
100
+ }) {
101
+ if (value is! Map) {
102
+ return NomosErrorInfo.legacy(
103
+ fallbackMessage,
104
+ operation: fallbackOperation,
105
+ trace: fallbackTrace,
106
+ );
107
+ }
108
+ final m = value.cast<String, Object?>();
109
+ final trace = NomosTraceContext.fromJson(m['trace']);
110
+ return NomosErrorInfo(
111
+ code: (m['code'] ?? 'nomos.bridge.error').toString(),
112
+ message: (m['message'] ??
113
+ fallbackMessage ??
114
+ 'the githolon refused the request')
115
+ .toString(),
116
+ component: (m['component'] ?? 'nomos.client').toString(),
117
+ operation: m['operation']?.toString() ?? fallbackOperation,
118
+ workspace: m['workspace']?.toString(),
119
+ domain: m['domain']?.toString(),
120
+ directiveId: m['directiveId']?.toString(),
121
+ trace: trace.isEmpty ? fallbackTrace : trace,
122
+ attributes:
123
+ ((m['attributes'] as Map?)?.cast<String, Object?>()) ?? const {},
124
+ cause: m['cause'] == null
125
+ ? null
126
+ : NomosErrorInfo.fromJson(m['cause'],
127
+ fallbackOperation: fallbackOperation),
128
+ );
129
+ }
130
+
131
+ factory NomosErrorInfo.fromBridgeMessage(
132
+ Map<String, Object?> message, {
133
+ String? fallbackOperation,
134
+ NomosTraceContext fallbackTrace = const NomosTraceContext(),
135
+ Object? fallbackMessage,
136
+ }) {
137
+ return NomosErrorInfo.fromJson(
138
+ message['errorInfo'],
139
+ fallbackMessage: message['error'] ?? fallbackMessage,
140
+ fallbackOperation: fallbackOperation,
141
+ fallbackTrace: fallbackTrace,
142
+ );
143
+ }
144
+
145
+ Map<String, Object?> toJson() => {
146
+ 'code': code,
147
+ 'message': message,
148
+ 'component': component,
149
+ if (operation != null) 'operation': operation,
150
+ if (workspace != null) 'workspace': workspace,
151
+ if (domain != null) 'domain': domain,
152
+ if (directiveId != null) 'directiveId': directiveId,
153
+ if (!trace.isEmpty) 'trace': trace.toJson(),
154
+ if (attributes.isNotEmpty) 'attributes': attributes,
155
+ if (cause != null) 'cause': cause!.toJson(),
156
+ };
157
+
158
+ /// Attribute map suitable for attaching to a future OTel span/event. The keys are stable and low-cardinality
159
+ /// where possible; high-cardinality values stay explicit (`workspace`, `domain`, directive) so apps can redact.
160
+ Map<String, Object?> toTelemetryAttributes() => {
161
+ 'exception.type': code,
162
+ 'exception.message': message,
163
+ 'nomos.error.code': code,
164
+ 'nomos.component': component,
165
+ if (operation != null) 'nomos.operation': operation,
166
+ if (workspace != null) 'nomos.workspace': workspace,
167
+ if (domain != null) 'nomos.domain': domain,
168
+ if (directiveId != null) 'nomos.directive_id': directiveId,
169
+ if (trace.traceId != null) 'trace_id': trace.traceId,
170
+ if (trace.spanId != null) 'span_id': trace.spanId,
171
+ ...attributes,
172
+ };
173
+ }
174
+
175
+ /// A typed refusal surfaced from the githolon's gate (a law refusal, a drift, a malformed op). Carries the
176
+ /// human-first message verbatim plus structured detail for logging, UI mapping, and future OTel export.
177
+ class NomosBridgeError implements Exception {
178
+ final String message;
179
+ final NomosErrorInfo info;
180
+ NomosBridgeError(this.message, {NomosErrorInfo? info})
181
+ : info = info ?? NomosErrorInfo.legacy(message);
182
+ @override
183
+ String toString() => 'NomosBridgeError: $message';
184
+ }
@@ -14,7 +14,7 @@
14
14
  /// All extend [NomosBridgeError] so existing `catch (NomosBridgeError)` handlers still catch them.
15
15
  library;
16
16
 
17
- import 'webview_bridge.dart' show NomosBridgeError, NomosErrorInfo;
17
+ import 'error_info.dart' show NomosBridgeError, NomosErrorInfo;
18
18
 
19
19
  /// Mint the MOST SPECIFIC typed error for a structured refusal — the one construction point the bridge
20
20
  /// uses everywhere it surfaces an `errorInfo`. A `nomos.custody.*` code becomes a [CustodyError] (typed,