@githolon/dsl 0.111.0 → 0.111.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.
package/dart/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.66.9
2
+
3
+ - Prepare delayed workspace enrollment inside Peer Office and retry a proven non-admitted write once. Generated applications can observe preparation while retaining their draft.
4
+
5
+ ## 0.66.8
6
+
7
+ - Recover expired or missing sync credentials from typed failures. Preserve authorization and domain refusals without refreshing credentials based on diagnostic wording or HTTP 403.
8
+
1
9
  ## 0.66.7
2
10
 
3
11
  - Recover sync after loss of the upstream bookmark by fetching provider history and allowing the kernel to verify a shared ancestor. Preserve pending local work and reject unrelated history through the existing admission gate.
@@ -18,7 +26,7 @@
18
26
 
19
27
  ## 0.66.3
20
28
 
21
- - Restaged the generated runtime bridge: regenerated vendored runtime assets. Published pub.dev archives are immutable, so regenerated bytes ride their own version.
29
+ - Recover expired or missing sync credentials from typed failures. Preserve authorization and domain refusals without refreshing credentials based on diagnostic wording or HTTP 403.
22
30
 
23
31
  ## 0.66.2
24
32
 
@@ -114,11 +122,11 @@
114
122
 
115
123
  ## 0.64.47
116
124
 
117
- - Restaged the generated runtime bridge: regenerated vendored runtime assets. Published pub.dev archives are immutable, so regenerated bytes ride their own version.
125
+ - Recover expired or missing sync credentials from typed failures. Preserve authorization and domain refusals without refreshing credentials based on diagnostic wording or HTTP 403.
118
126
 
119
127
  ## 0.64.46
120
128
 
121
- - Restaged the generated runtime bridge: regenerated vendored runtime assets. Published pub.dev archives are immutable, so regenerated bytes ride their own version.
129
+ - Recover expired or missing sync credentials from typed failures. Preserve authorization and domain refusals without refreshing credentials based on diagnostic wording or HTTP 403.
122
130
 
123
131
  ## 0.64.45
124
132
 
@@ -17,6 +17,7 @@ library;
17
17
  import 'package:meta/meta.dart' show internal;
18
18
 
19
19
  import 'dart:async';
20
+ import '../write_progress.dart';
20
21
  import 'dart:convert';
21
22
  import 'dart:developer' as developer;
22
23
  import 'dart:typed_data';
@@ -879,7 +880,16 @@ class DartReception {
879
880
  );
880
881
  }
881
882
 
883
+ final Map<String, void Function(NomosWriteProgress)> _writeObservers = {};
884
+
882
885
  void _onRuntimeEvent(nomos_protocol.RuntimeEvent event) {
886
+ if (event.hasWritePreparation()) {
887
+ final progress = event.writePreparation;
888
+ _eventPlane._writeObservers[progress.requestId]?.call(
889
+ const NomosWriteProgress(NomosWritePhase.preparing),
890
+ );
891
+ return;
892
+ }
883
893
  if (event.hasReady()) {
884
894
  final stale = _staleBundleError(event.ready.runtimeVersion);
885
895
  if (stale != null && !allowStaleRunnerBundle) {
@@ -1719,40 +1729,48 @@ class DartReception {
1719
1729
  // ignore: avoid_print
1720
1730
  print('[nomos-offer] dispatch ${call.domain}/${call.directive} in '
1721
1731
  '$workspace using ${authenticationProof.name}');
1722
- Future<nomos_protocol.CallResponse> attempt() {
1732
+ Future<nomos_protocol.CallResponse> attempt() async {
1723
1733
  final requestNumber = ++plane._sequences.request;
1724
- return kernelTransport.callKernel(
1725
- session: runtimeSession,
1726
- request: nomos_protocol.CallRequest(
1727
- protocolVersion: 1,
1728
- requestId: '$runtimeSession:offer:$requestNumber',
1729
- offer: nomos_protocol.OfferRequest(
1730
- call: nomos_protocol.DirectiveCall(
1731
- domain: call.domain,
1732
- directive: call.directive,
1733
- operation: google_protobuf.Any(
1734
- typeUrl: call.protobufType,
1735
- value: call.protobufPayload,
1734
+ final requestId = '$runtimeSession:offer:$requestNumber';
1735
+ final observer = NomosWrite.captureObserver();
1736
+ if (observer != null) plane._writeObservers[requestId] = observer;
1737
+ try {
1738
+ return await kernelTransport.callKernel(
1739
+ session: runtimeSession,
1740
+ request: nomos_protocol.CallRequest(
1741
+ protocolVersion: 1,
1742
+ requestId: requestId,
1743
+ offer: nomos_protocol.OfferRequest(
1744
+ call: nomos_protocol.DirectiveCall(
1745
+ domain: call.domain,
1746
+ directive: call.directive,
1747
+ operation: google_protobuf.Any(
1748
+ typeUrl: call.protobufType,
1749
+ value: call.protobufPayload,
1750
+ ),
1736
1751
  ),
1752
+ authoring: nomos_protocol.AuthoringOptions(
1753
+ actor: actor ?? '',
1754
+ // Ordinary generated calls select a peer-runtime credential
1755
+ // capability below. An explicit secret is a temporary operator
1756
+ // compatibility lane and remains client-supplied until removed.
1757
+ authorSecret: explicitSecret == null
1758
+ ? const <int>[]
1759
+ : base64Decode(explicitSecret),
1760
+ deferProjection: true,
1761
+ spans: true,
1762
+ ),
1763
+ controllerLawHash: domainHash,
1764
+ conflictSnapshots: conflictSnapshots,
1765
+ birthCertificate: birthCertificate,
1737
1766
  ),
1738
- authoring: nomos_protocol.AuthoringOptions(
1739
- actor: actor ?? '',
1740
- // Ordinary generated calls select a peer-runtime credential
1741
- // capability below. An explicit secret is a temporary operator
1742
- // compatibility lane and remains client-supplied until removed.
1743
- authorSecret: explicitSecret == null
1744
- ? const <int>[]
1745
- : base64Decode(explicitSecret),
1746
- deferProjection: true,
1747
- spans: true,
1748
- ),
1749
- controllerLawHash: domainHash,
1750
- conflictSnapshots: conflictSnapshots,
1751
- birthCertificate: birthCertificate,
1752
1767
  ),
1753
- ),
1754
- credentialUse: credentialUse,
1755
- );
1768
+ credentialUse: credentialUse,
1769
+ );
1770
+ } finally {
1771
+ plane._writeObservers.remove(requestId);
1772
+ observer?.call(const NomosWriteProgress(NomosWritePhase.running));
1773
+ }
1756
1774
  }
1757
1775
 
1758
1776
  var response = await attempt();
@@ -5995,7 +5995,7 @@ var CustodyBreakPolicySchema = /* @__PURE__ */ enumDesc(file_nomos_runtime_v2_re
5995
5995
  var CustodyBreakPolicy = /* @__PURE__ */ tsEnum(CustodyBreakPolicySchema);
5996
5996
 
5997
5997
  // ../../adapter/proto/nomos/runtime/v2/observability_desk_pb.js
5998
- var file_nomos_runtime_v2_observability_desk = /* @__PURE__ */ fileDesc("Cilub21vcy9ydW50aW1lL3YyL29ic2VydmFiaWxpdHlfZGVzay5wcm90bxIQbm9tb3MucnVudGltZS52MiJCCgxUcmFjZUNvbnRleHQSEAoIdHJhY2VfaWQYASABKAwSDwoHc3Bhbl9pZBgCIAEoDBIPCgdzYW1wbGVkGAMgASgIIp0CCg5SdW50aW1lRmFpbHVyZRIMCgRjb2RlGAEgASgJEg8KB21lc3NhZ2UYAiABKAkSEQoJY29tcG9uZW50GAMgASgJEhYKCXdvcmtzcGFjZRgEIAEoCUgAiAEBEhMKBmRvbWFpbhgFIAEoCUgBiAEBEhYKCWRpcmVjdGl2ZRgGIAEoCUgCiAEBEjkKCmF0dHJpYnV0ZXMYByADKAsyJS5ub21vcy5ydW50aW1lLnYyLkRpYWdub3N0aWNBdHRyaWJ1dGUSMgoEa2luZBgIIAEoDjIkLm5vbW9zLnJ1bnRpbWUudjIuUnVudGltZUZhaWx1cmVLaW5kQgwKCl93b3Jrc3BhY2VCCQoHX2RvbWFpbkIMCgpfZGlyZWN0aXZlIr4BChJSdW50aW1lVW5hdmFpbGFibGUSOgoGcmVhc29uGAEgASgOMioubm9tb3MucnVudGltZS52Mi5SdW50aW1lVW5hdmFpbGFibGVSZWFzb24SEQoJd29ya3NwYWNlGAIgASgJEhEKCXJldHJ5YWJsZRgDIAEoCBIfChJyZXRyeV9hZnRlcl9taWxsaXMYBCABKA1IAIgBARIOCgZkZXRhaWwYBSABKAlCFQoTX3JldHJ5X2FmdGVyX21pbGxpcyJ+Cg5SdW50aW1lUmVmdXNhbBI2CgZyZWFzb24YASABKA4yJi5ub21vcy5ydW50aW1lLnYyLlJ1bnRpbWVSZWZ1c2FsUmVhc29uEhEKCXdvcmtzcGFjZRgCIAEoCRIRCglyZXRyeWFibGUYAyABKAgSDgoGZGV0YWlsGAQgASgJIqUBChNEaWFnbm9zdGljQXR0cmlidXRlEgwKBG5hbWUYASABKAkSFgoMc3RyaW5nX3ZhbHVlGAIgASgJSAASFwoNaW50ZWdlcl92YWx1ZRgDIAEoEkgAEhYKDGRvdWJsZV92YWx1ZRgEIAEoAUgAEhcKDWJvb2xlYW5fdmFsdWUYBSABKAhIABIVCgtieXRlc192YWx1ZRgGIAEoDEgAQgcKBXZhbHVlIuMKCgxSdW50aW1lRXZlbnQSEAoIc2VxdWVuY2UYASABKAQSFAoHc2Vzc2lvbhgCIAEoCUgBiAEBEi8KBXJlYWR5GAogASgLMh4ubm9tb3MucnVudGltZS52Mi5SdW50aW1lUmVhZHlIABI3Cg13YXRjaF9jaGFuZ2VkGAsgASgLMh4ubm9tb3MucnVudGltZS52Mi5XYXRjaENoYW5nZWRIABI5Cg5zeW5jX2NvbXBsZXRlZBgMIAEoCzIfLm5vbW9zLnJ1bnRpbWUudjIuU3luY0NvbXBsZXRlZEgAEjUKDGxpbmtfY2hhbmdlZBgNIAEoCzIdLm5vbW9zLnJ1bnRpbWUudjIuTGlua0NoYW5nZWRIABI5Cg5zaWduYWxfY2hhbmdlZBgOIAEoCzIfLm5vbW9zLnJ1bnRpbWUudjIuU2lnbmFsQ2hhbmdlZEgAEjsKD2N1c3RvZHlfY2hhbmdlZBgPIAEoCzIgLm5vbW9zLnJ1bnRpbWUudjIuQ3VzdG9keUNoYW5nZWRIABJCChNsb2NhbF93b3JrX3JlamVjdGVkGBAgASgLMiMubm9tb3MucnVudGltZS52Mi5Mb2NhbFdvcmtSZWplY3RlZEgAEjMKC2xhd19jaGFuZ2VkGBEgASgLMhwubm9tb3MucnVudGltZS52Mi5MYXdDaGFuZ2VkSAASQwoPa2VybmVsX3Byb2dyZXNzGBIgASgLMigubm9tb3MucnVudGltZS52Mi5LZXJuZWxQcm9ncmVzc09ic2VydmVkSAASLQoEc3BhbhgTIAEoCzIdLm5vbW9zLnJ1bnRpbWUudjIuUnVudGltZVNwYW5IABI/ChF0cmFuc2Zlcl9wcm9ncmVzcxgUIAEoCzIiLm5vbW9zLnJ1bnRpbWUudjIuVHJhbnNmZXJQcm9ncmVzc0gAEkkKFG1haW50ZW5hbmNlX3Byb2dyZXNzGBUgASgLMikubm9tb3MucnVudGltZS52Mi5SZWFkTWFpbnRlbmFuY2VQcm9ncmVzc0gAEjsKDXNvY2tldF9lZmZlY3QYFiABKAsyIi5ub21vcy5ydW50aW1lLnYyLlBlZXJTb2NrZXRFZmZlY3RIABJUChxjcmVkZW50aWFsX3JlZnJlc2hfcmVxdWVzdGVkGBcgASgLMiwubm9tb3MucnVudGltZS52Mi5DcmVkZW50aWFsUmVmcmVzaFJlcXVlc3RlZEgAElIKG3JlY2VpdmluZ19ncm91cF9jb252ZXJnZW5jZRgYIAEoCzIrLm5vbW9zLnJ1bnRpbWUudjIuUmVjZWl2aW5nR3JvdXBDb252ZXJnZW5jZUgAEkoKF21vZGVsX2Fkb3B0aW9uX3Byb2dyZXNzGBkgASgLMicubm9tb3MucnVudGltZS52Mi5Nb2RlbEFkb3B0aW9uUHJvZ3Jlc3NIABI7Cg9mb2xsb3dzX3N1bW1hcnkYGiABKAsyIC5ub21vcy5ydW50aW1lLnYyLkZvbGxvd3NTdW1tYXJ5SAASRAoUbG9jYWxfY29weV9wcmVzZXJ2ZWQYGyABKAsyJC5ub21vcy5ydW50aW1lLnYyLlByZXNlcnZlZExvY2FsQ29weUgAEk4KGWZvcmVpZ25fcGxhY2VtZW50X2lnbm9yZWQYHCABKAsyKS5ub21vcy5ydW50aW1lLnYyLkZvcmVpZ25QbGFjZW1lbnRJZ25vcmVkSAASQwoTYmluZGluZ19xdWFyYW50aW5lZBgdIAEoCzIkLm5vbW9zLnJ1bnRpbWUudjIuUXVhcmFudGluZWRCaW5kaW5nSABCBwoFZXZlbnRCCgoIX3Nlc3Npb24ivAEKDkZvbGxvd3NTdW1tYXJ5EhAKCGZvbGxvd2VkGAEgASgNEg8KB3NldHRsZWQYAiABKA0SDAoEbGl2ZRgDIAEoDRI3Cgp3b3Jrc3BhY2VzGAQgAygLMiMubm9tb3MucnVudGltZS52Mi5Gb2xsb3dPYnNlcnZhdGlvbhIkChdvYnNlcnZlZF9hdF91bml4X21pbGxpcxgFIAEoBEgAiAEBQhoKGF9vYnNlcnZlZF9hdF91bml4X21pbGxpcyLRAgoRRm9sbG93T2JzZXJ2YXRpb24SEQoJd29ya3NwYWNlGAEgASgJEjcKBXBoYXNlGAIgASgOMigubm9tb3MucnVudGltZS52Mi5Gb2xsb3dPYnNlcnZhdGlvblBoYXNlEgwKBGxpdmUYAyABKAgSEwoGZGV0YWlsGAQgASgJSACIAQESIQoUcmV0cnlfYXRfdW5peF9taWxsaXMYBSABKARIAYgBARIfChJkdWVfYXRfdW5peF9taWxsaXMYBiABKARIAogBARIUCgdhdHRlbXB0GAcgASgNSAOIAQESGgoNYWN0aXZlX2VmZmVjdBgIIAEoCUgEiAEBQgkKB19kZXRhaWxCFwoVX3JldHJ5X2F0X3VuaXhfbWlsbGlzQhUKE19kdWVfYXRfdW5peF9taWxsaXNCCgoIX2F0dGVtcHRCEAoOX2FjdGl2ZV9lZmZlY3QiVgoaQ3JlZGVudGlhbFJlZnJlc2hSZXF1ZXN0ZWQSOAoHcGVuZGluZxgBIAEoCzInLm5vbW9zLnJ1bnRpbWUudjIuQ29ycmVzcG9uZGVuY2VQZW5kaW5nIj4KDFJ1bnRpbWVSZWFkeRIXCg9ydW50aW1lX3ZlcnNpb24YASABKAkSFQoNa2VybmVsX3NoYTI1NhgCIAEoDCL8AQoMV2F0Y2hDaGFuZ2VkEhcKD3N1YnNjcmlwdGlvbl9pZBgBIAEoCRITCgtyZXN1bHRfdXNkYRgCIAEoDBI2CgdmYWlsdXJlGAMgASgLMiAubm9tb3MucnVudGltZS52Mi5SdW50aW1lRmFpbHVyZUgAiAEBEjUKEmFwcGxpY2F0aW9uX3Jlc3VsdBgEIAEoCzIULmdvb2dsZS5wcm90b2J1Zi5BbnlIAYgBARIaCg1pbnRlZ2VyX3ZhbHVlGAUgASgDSAKIAQFCCgoIX2ZhaWx1cmVCFQoTX2FwcGxpY2F0aW9uX3Jlc3VsdEIQCg5faW50ZWdlcl92YWx1ZSKcAQoNU3luY0NvbXBsZXRlZBIOCgZyZWFzb24YASABKAkSNwoGcmVzdWx0GAIgASgLMicubm9tb3MucnVudGltZS52Mi5TeW5jV29ya3NwYWNlUmVzcG9uc2USNgoHZmFpbHVyZRgDIAEoCzIgLm5vbW9zLnJ1bnRpbWUudjIuUnVudGltZUZhaWx1cmVIAIgBAUIKCghfZmFpbHVyZSI7CgtMaW5rQ2hhbmdlZBIsCgZzdGF0dXMYASABKA4yHC5ub21vcy5ydW50aW1lLnYyLkxpbmtTdGF0dXMiOAoNU2lnbmFsQ2hhbmdlZBIOCgZkb21haW4YASABKAkSFwoPcHJvamVjdGlvbl91c2RhGAIgASgMIlAKCkxhd0NoYW5nZWQSDgoGZG9tYWluGAEgASgJEhYKDnBhY2thZ2Vfc2hhMjU2GAIgASgJEhoKEmNvbXBhdGliaWxpdHlfdXNkYRgDIAEoDCLdAQoLUnVudGltZVNwYW4SEAoIdHJhY2VfaWQYASABKAwSDwoHc3Bhbl9pZBgCIAEoDBIbCg5wYXJlbnRfc3Bhbl9pZBgDIAEoDEgAiAEBEgwKBG5hbWUYBCABKAkSGgoSc3RhcnRlZF91bml4X25hbm9zGAUgASgEEhYKDmR1cmF0aW9uX25hbm9zGAYgASgEEjkKCmF0dHJpYnV0ZXMYByADKAsyJS5ub21vcy5ydW50aW1lLnYyLkRpYWdub3N0aWNBdHRyaWJ1dGVCEQoPX3BhcmVudF9zcGFuX2lkIuoBChZLZXJuZWxQcm9ncmVzc09ic2VydmVkEjkKCHNuYXBzaG90GAEgASgLMicubm9tb3Mua2VybmVsLnYxLktlcm5lbFByb2dyZXNzU25hcHNob3QSJAocaG9zdF9vYnNlcnZlZF9lbGFwc2VkX21pY3JvcxgCIAEoBBIqCiJob3N0X29ic2VydmVkX3BoYXNlX2VsYXBzZWRfbWljcm9zGAMgASgEEh4KFnByb2Nlc3NfcmVzaWRlbnRfYnl0ZXMYBCABKAQSIwobcHJvY2Vzc19wZWFrX3Jlc2lkZW50X2J5dGVzGAUgASgEIoIBChBUcmFuc2ZlclByb2dyZXNzEhYKDnJlY2VpdmVkX2J5dGVzGAEgASgEEhgKC3RvdGFsX2J5dGVzGAIgASgESACIAQESLAoEa2luZBgDIAEoDjIeLm5vbW9zLnJ1bnRpbWUudjIuVHJhbnNmZXJLaW5kQg4KDF90b3RhbF9ieXRlcyrKAQoSUnVudGltZUZhaWx1cmVLaW5kEiQKIFJVTlRJTUVfRkFJTFVSRV9LSU5EX1VOU1BFQ0lGSUVEEAASIQodUlVOVElNRV9GQUlMVVJFX0tJTkRfSU5URVJOQUwQARIhCh1SVU5USU1FX0ZBSUxVUkVfS0lORF9QUk9UT0NPTBACEiIKHlJVTlRJTUVfRkFJTFVSRV9LSU5EX0lOVEVHUklUWRADEiQKIFJVTlRJTUVfRkFJTFVSRV9LSU5EX1VOU1VQUE9SVEVEEAQqpwMKGFJ1bnRpbWVVbmF2YWlsYWJsZVJlYXNvbhIqCiZSVU5USU1FX1VOQVZBSUxBQkxFX1JFQVNPTl9VTlNQRUNJRklFRBAAEjIKLlJVTlRJTUVfVU5BVkFJTEFCTEVfUkVBU09OX1JFTU9URV9PTkxZX09GRkxJTkUQARIxCi1SVU5USU1FX1VOQVZBSUxBQkxFX1JFQVNPTl9SRU1PVEVfVU5SRUFDSEFCTEUQAhItCilSVU5USU1FX1VOQVZBSUxBQkxFX1JFQVNPTl9SRU1PVEVfVElNRU9VVBADEjYKMlJVTlRJTUVfVU5BVkFJTEFCTEVfUkVBU09OX0xPQ0FMX1JFUExJQ0FfTk9UX1JFQURZEAQSNQoxUlVOVElNRV9VTkFWQUlMQUJMRV9SRUFTT05fQ1JFREVOVElBTF9VTkFWQUlMQUJMRRAFEjAKLFJVTlRJTUVfVU5BVkFJTEFCTEVfUkVBU09OX1dPUktTUEFDRV9PUEVOSU5HEAYSKAokUlVOVElNRV9VTkFWQUlMQUJMRV9SRUFTT05fUEVFUl9CVVNZEAcq8AEKFFJ1bnRpbWVSZWZ1c2FsUmVhc29uEiYKIlJVTlRJTUVfUkVGVVNBTF9SRUFTT05fVU5TUEVDSUZJRUQQABItCilSVU5USU1FX1JFRlVTQUxfUkVBU09OX0lOVkFMSURfQ1JFREVOVElBTBABEigKJFJVTlRJTUVfUkVGVVNBTF9SRUFTT05fQUNDRVNTX0RFTklFRBACEisKJ1JVTlRJTUVfUkVGVVNBTF9SRUFTT05fUExBQ0VNRU5UX1BPTElDWRADEioKJlJVTlRJTUVfUkVGVVNBTF9SRUFTT05fU0VTU0lPTl9VTkJPVU5EEAQqlQQKFkZvbGxvd09ic2VydmF0aW9uUGhhc2USKAokRk9MTE9XX09CU0VSVkFUSU9OX1BIQVNFX1VOU1BFQ0lGSUVEEAASJAogRk9MTE9XX09CU0VSVkFUSU9OX1BIQVNFX0RFU0lSRUQQARIgChxGT0xMT1dfT0JTRVJWQVRJT05fUEhBU0VfRFVFEAISJAogRk9MTE9XX09CU0VSVkFUSU9OX1BIQVNFX1JVTk5JTkcQAxIkCiBGT0xMT1dfT0JTRVJWQVRJT05fUEhBU0VfU0VUVExFRBAEEjAKLEZPTExPV19PQlNFUlZBVElPTl9QSEFTRV9BV0FJVElOR19ERVBFTkRFTkNZEAUSJAogRk9MTE9XX09CU0VSVkFUSU9OX1BIQVNFX09GRkxJTkUQBhIvCitGT0xMT1dfT0JTRVJWQVRJT05fUEhBU0VfSU5WQUxJRF9DUkVERU5USUFMEAcSJAogRk9MTE9XX09CU0VSVkFUSU9OX1BIQVNFX0JBQ0tPRkYQCBIsCihGT0xMT1dfT0JTRVJWQVRJT05fUEhBU0VfUkVCT1JOX1JFTU9URUxZEAkSLAooRk9MTE9XX09CU0VSVkFUSU9OX1BIQVNFX1JFUExBQ0lOR19MT0NBTBAKEjIKLkZPTExPV19PQlNFUlZBVElPTl9QSEFTRV9MT0NBTF9DT1BZX1VOUkVBREFCTEUQCypXCgpMaW5rU3RhdHVzEhsKF0xJTktfU1RBVFVTX1VOU1BFQ0lGSUVEEAASFAoQTElOS19TVEFUVVNfT1BFThABEhYKEkxJTktfU1RBVFVTX0NMT1NFRBACKnYKDFRyYW5zZmVyS2luZBIdChlUUkFOU0ZFUl9LSU5EX1VOU1BFQ0lGSUVEEAASFwoTVFJBTlNGRVJfS0lORF9ERUxUQRABEhYKElRSQU5TRkVSX0tJTkRfRlVMTBACEhYKElRSQU5TRkVSX0tJTkRfUFVTSBADYgZwcm90bzM", [file_google_protobuf_any, file_nomos_kernel_v1_progress, file_nomos_runtime_v2_correspondence_desk, file_nomos_runtime_v2_custody_desk, file_nomos_runtime_v2_placement_desk, file_nomos_runtime_v2_maintenance_desk, file_nomos_runtime_v2_model_adoption_desk, file_nomos_runtime_v2_receiving_group_desk, file_nomos_runtime_v2_recovery_desk]);
5998
+ var file_nomos_runtime_v2_observability_desk = /* @__PURE__ */ fileDesc("Cilub21vcy9ydW50aW1lL3YyL29ic2VydmFiaWxpdHlfZGVzay5wcm90bxIQbm9tb3MucnVudGltZS52MiJCCgxUcmFjZUNvbnRleHQSEAoIdHJhY2VfaWQYASABKAwSDwoHc3Bhbl9pZBgCIAEoDBIPCgdzYW1wbGVkGAMgASgIIp0CCg5SdW50aW1lRmFpbHVyZRIMCgRjb2RlGAEgASgJEg8KB21lc3NhZ2UYAiABKAkSEQoJY29tcG9uZW50GAMgASgJEhYKCXdvcmtzcGFjZRgEIAEoCUgAiAEBEhMKBmRvbWFpbhgFIAEoCUgBiAEBEhYKCWRpcmVjdGl2ZRgGIAEoCUgCiAEBEjkKCmF0dHJpYnV0ZXMYByADKAsyJS5ub21vcy5ydW50aW1lLnYyLkRpYWdub3N0aWNBdHRyaWJ1dGUSMgoEa2luZBgIIAEoDjIkLm5vbW9zLnJ1bnRpbWUudjIuUnVudGltZUZhaWx1cmVLaW5kQgwKCl93b3Jrc3BhY2VCCQoHX2RvbWFpbkIMCgpfZGlyZWN0aXZlIr4BChJSdW50aW1lVW5hdmFpbGFibGUSOgoGcmVhc29uGAEgASgOMioubm9tb3MucnVudGltZS52Mi5SdW50aW1lVW5hdmFpbGFibGVSZWFzb24SEQoJd29ya3NwYWNlGAIgASgJEhEKCXJldHJ5YWJsZRgDIAEoCBIfChJyZXRyeV9hZnRlcl9taWxsaXMYBCABKA1IAIgBARIOCgZkZXRhaWwYBSABKAlCFQoTX3JldHJ5X2FmdGVyX21pbGxpcyJ+Cg5SdW50aW1lUmVmdXNhbBI2CgZyZWFzb24YASABKA4yJi5ub21vcy5ydW50aW1lLnYyLlJ1bnRpbWVSZWZ1c2FsUmVhc29uEhEKCXdvcmtzcGFjZRgCIAEoCRIRCglyZXRyeWFibGUYAyABKAgSDgoGZGV0YWlsGAQgASgJIqUBChNEaWFnbm9zdGljQXR0cmlidXRlEgwKBG5hbWUYASABKAkSFgoMc3RyaW5nX3ZhbHVlGAIgASgJSAASFwoNaW50ZWdlcl92YWx1ZRgDIAEoEkgAEhYKDGRvdWJsZV92YWx1ZRgEIAEoAUgAEhcKDWJvb2xlYW5fdmFsdWUYBSABKAhIABIVCgtieXRlc192YWx1ZRgGIAEoDEgAQgcKBXZhbHVlIqQLCgxSdW50aW1lRXZlbnQSEAoIc2VxdWVuY2UYASABKAQSFAoHc2Vzc2lvbhgCIAEoCUgBiAEBEi8KBXJlYWR5GAogASgLMh4ubm9tb3MucnVudGltZS52Mi5SdW50aW1lUmVhZHlIABI3Cg13YXRjaF9jaGFuZ2VkGAsgASgLMh4ubm9tb3MucnVudGltZS52Mi5XYXRjaENoYW5nZWRIABI5Cg5zeW5jX2NvbXBsZXRlZBgMIAEoCzIfLm5vbW9zLnJ1bnRpbWUudjIuU3luY0NvbXBsZXRlZEgAEjUKDGxpbmtfY2hhbmdlZBgNIAEoCzIdLm5vbW9zLnJ1bnRpbWUudjIuTGlua0NoYW5nZWRIABI5Cg5zaWduYWxfY2hhbmdlZBgOIAEoCzIfLm5vbW9zLnJ1bnRpbWUudjIuU2lnbmFsQ2hhbmdlZEgAEjsKD2N1c3RvZHlfY2hhbmdlZBgPIAEoCzIgLm5vbW9zLnJ1bnRpbWUudjIuQ3VzdG9keUNoYW5nZWRIABJCChNsb2NhbF93b3JrX3JlamVjdGVkGBAgASgLMiMubm9tb3MucnVudGltZS52Mi5Mb2NhbFdvcmtSZWplY3RlZEgAEjMKC2xhd19jaGFuZ2VkGBEgASgLMhwubm9tb3MucnVudGltZS52Mi5MYXdDaGFuZ2VkSAASQwoPa2VybmVsX3Byb2dyZXNzGBIgASgLMigubm9tb3MucnVudGltZS52Mi5LZXJuZWxQcm9ncmVzc09ic2VydmVkSAASLQoEc3BhbhgTIAEoCzIdLm5vbW9zLnJ1bnRpbWUudjIuUnVudGltZVNwYW5IABI/ChF0cmFuc2Zlcl9wcm9ncmVzcxgUIAEoCzIiLm5vbW9zLnJ1bnRpbWUudjIuVHJhbnNmZXJQcm9ncmVzc0gAEkkKFG1haW50ZW5hbmNlX3Byb2dyZXNzGBUgASgLMikubm9tb3MucnVudGltZS52Mi5SZWFkTWFpbnRlbmFuY2VQcm9ncmVzc0gAEjsKDXNvY2tldF9lZmZlY3QYFiABKAsyIi5ub21vcy5ydW50aW1lLnYyLlBlZXJTb2NrZXRFZmZlY3RIABJUChxjcmVkZW50aWFsX3JlZnJlc2hfcmVxdWVzdGVkGBcgASgLMiwubm9tb3MucnVudGltZS52Mi5DcmVkZW50aWFsUmVmcmVzaFJlcXVlc3RlZEgAElIKG3JlY2VpdmluZ19ncm91cF9jb252ZXJnZW5jZRgYIAEoCzIrLm5vbW9zLnJ1bnRpbWUudjIuUmVjZWl2aW5nR3JvdXBDb252ZXJnZW5jZUgAEkoKF21vZGVsX2Fkb3B0aW9uX3Byb2dyZXNzGBkgASgLMicubm9tb3MucnVudGltZS52Mi5Nb2RlbEFkb3B0aW9uUHJvZ3Jlc3NIABI7Cg9mb2xsb3dzX3N1bW1hcnkYGiABKAsyIC5ub21vcy5ydW50aW1lLnYyLkZvbGxvd3NTdW1tYXJ5SAASRAoUbG9jYWxfY29weV9wcmVzZXJ2ZWQYGyABKAsyJC5ub21vcy5ydW50aW1lLnYyLlByZXNlcnZlZExvY2FsQ29weUgAEk4KGWZvcmVpZ25fcGxhY2VtZW50X2lnbm9yZWQYHCABKAsyKS5ub21vcy5ydW50aW1lLnYyLkZvcmVpZ25QbGFjZW1lbnRJZ25vcmVkSAASQwoTYmluZGluZ19xdWFyYW50aW5lZBgdIAEoCzIkLm5vbW9zLnJ1bnRpbWUudjIuUXVhcmFudGluZWRCaW5kaW5nSAASPwoRd3JpdGVfcHJlcGFyYXRpb24YHiABKAsyIi5ub21vcy5ydW50aW1lLnYyLldyaXRlUHJlcGFyYXRpb25IAEIHCgVldmVudEIKCghfc2Vzc2lvbiK8AQoORm9sbG93c1N1bW1hcnkSEAoIZm9sbG93ZWQYASABKA0SDwoHc2V0dGxlZBgCIAEoDRIMCgRsaXZlGAMgASgNEjcKCndvcmtzcGFjZXMYBCADKAsyIy5ub21vcy5ydW50aW1lLnYyLkZvbGxvd09ic2VydmF0aW9uEiQKF29ic2VydmVkX2F0X3VuaXhfbWlsbGlzGAUgASgESACIAQFCGgoYX29ic2VydmVkX2F0X3VuaXhfbWlsbGlzItECChFGb2xsb3dPYnNlcnZhdGlvbhIRCgl3b3Jrc3BhY2UYASABKAkSNwoFcGhhc2UYAiABKA4yKC5ub21vcy5ydW50aW1lLnYyLkZvbGxvd09ic2VydmF0aW9uUGhhc2USDAoEbGl2ZRgDIAEoCBITCgZkZXRhaWwYBCABKAlIAIgBARIhChRyZXRyeV9hdF91bml4X21pbGxpcxgFIAEoBEgBiAEBEh8KEmR1ZV9hdF91bml4X21pbGxpcxgGIAEoBEgCiAEBEhQKB2F0dGVtcHQYByABKA1IA4gBARIaCg1hY3RpdmVfZWZmZWN0GAggASgJSASIAQFCCQoHX2RldGFpbEIXChVfcmV0cnlfYXRfdW5peF9taWxsaXNCFQoTX2R1ZV9hdF91bml4X21pbGxpc0IKCghfYXR0ZW1wdEIQCg5fYWN0aXZlX2VmZmVjdCJWChpDcmVkZW50aWFsUmVmcmVzaFJlcXVlc3RlZBI4CgdwZW5kaW5nGAEgASgLMicubm9tb3MucnVudGltZS52Mi5Db3JyZXNwb25kZW5jZVBlbmRpbmciPgoMUnVudGltZVJlYWR5EhcKD3J1bnRpbWVfdmVyc2lvbhgBIAEoCRIVCg1rZXJuZWxfc2hhMjU2GAIgASgMIvwBCgxXYXRjaENoYW5nZWQSFwoPc3Vic2NyaXB0aW9uX2lkGAEgASgJEhMKC3Jlc3VsdF91c2RhGAIgASgMEjYKB2ZhaWx1cmUYAyABKAsyIC5ub21vcy5ydW50aW1lLnYyLlJ1bnRpbWVGYWlsdXJlSACIAQESNQoSYXBwbGljYXRpb25fcmVzdWx0GAQgASgLMhQuZ29vZ2xlLnByb3RvYnVmLkFueUgBiAEBEhoKDWludGVnZXJfdmFsdWUYBSABKANIAogBAUIKCghfZmFpbHVyZUIVChNfYXBwbGljYXRpb25fcmVzdWx0QhAKDl9pbnRlZ2VyX3ZhbHVlIpwBCg1TeW5jQ29tcGxldGVkEg4KBnJlYXNvbhgBIAEoCRI3CgZyZXN1bHQYAiABKAsyJy5ub21vcy5ydW50aW1lLnYyLlN5bmNXb3Jrc3BhY2VSZXNwb25zZRI2CgdmYWlsdXJlGAMgASgLMiAubm9tb3MucnVudGltZS52Mi5SdW50aW1lRmFpbHVyZUgAiAEBQgoKCF9mYWlsdXJlIjsKC0xpbmtDaGFuZ2VkEiwKBnN0YXR1cxgBIAEoDjIcLm5vbW9zLnJ1bnRpbWUudjIuTGlua1N0YXR1cyI4Cg1TaWduYWxDaGFuZ2VkEg4KBmRvbWFpbhgBIAEoCRIXCg9wcm9qZWN0aW9uX3VzZGEYAiABKAwiUAoKTGF3Q2hhbmdlZBIOCgZkb21haW4YASABKAkSFgoOcGFja2FnZV9zaGEyNTYYAiABKAkSGgoSY29tcGF0aWJpbGl0eV91c2RhGAMgASgMIt0BCgtSdW50aW1lU3BhbhIQCgh0cmFjZV9pZBgBIAEoDBIPCgdzcGFuX2lkGAIgASgMEhsKDnBhcmVudF9zcGFuX2lkGAMgASgMSACIAQESDAoEbmFtZRgEIAEoCRIaChJzdGFydGVkX3VuaXhfbmFub3MYBSABKAQSFgoOZHVyYXRpb25fbmFub3MYBiABKAQSOQoKYXR0cmlidXRlcxgHIAMoCzIlLm5vbW9zLnJ1bnRpbWUudjIuRGlhZ25vc3RpY0F0dHJpYnV0ZUIRCg9fcGFyZW50X3NwYW5faWQi6gEKFktlcm5lbFByb2dyZXNzT2JzZXJ2ZWQSOQoIc25hcHNob3QYASABKAsyJy5ub21vcy5rZXJuZWwudjEuS2VybmVsUHJvZ3Jlc3NTbmFwc2hvdBIkChxob3N0X29ic2VydmVkX2VsYXBzZWRfbWljcm9zGAIgASgEEioKImhvc3Rfb2JzZXJ2ZWRfcGhhc2VfZWxhcHNlZF9taWNyb3MYAyABKAQSHgoWcHJvY2Vzc19yZXNpZGVudF9ieXRlcxgEIAEoBBIjChtwcm9jZXNzX3BlYWtfcmVzaWRlbnRfYnl0ZXMYBSABKAQiggEKEFRyYW5zZmVyUHJvZ3Jlc3MSFgoOcmVjZWl2ZWRfYnl0ZXMYASABKAQSGAoLdG90YWxfYnl0ZXMYAiABKARIAIgBARIsCgRraW5kGAMgASgOMh4ubm9tb3MucnVudGltZS52Mi5UcmFuc2ZlcktpbmRCDgoMX3RvdGFsX2J5dGVzIjkKEFdyaXRlUHJlcGFyYXRpb24SEgoKcmVxdWVzdF9pZBgBIAEoCRIRCgl3b3Jrc3BhY2UYAiABKAkqygEKElJ1bnRpbWVGYWlsdXJlS2luZBIkCiBSVU5USU1FX0ZBSUxVUkVfS0lORF9VTlNQRUNJRklFRBAAEiEKHVJVTlRJTUVfRkFJTFVSRV9LSU5EX0lOVEVSTkFMEAESIQodUlVOVElNRV9GQUlMVVJFX0tJTkRfUFJPVE9DT0wQAhIiCh5SVU5USU1FX0ZBSUxVUkVfS0lORF9JTlRFR1JJVFkQAxIkCiBSVU5USU1FX0ZBSUxVUkVfS0lORF9VTlNVUFBPUlRFRBAEKqcDChhSdW50aW1lVW5hdmFpbGFibGVSZWFzb24SKgomUlVOVElNRV9VTkFWQUlMQUJMRV9SRUFTT05fVU5TUEVDSUZJRUQQABIyCi5SVU5USU1FX1VOQVZBSUxBQkxFX1JFQVNPTl9SRU1PVEVfT05MWV9PRkZMSU5FEAESMQotUlVOVElNRV9VTkFWQUlMQUJMRV9SRUFTT05fUkVNT1RFX1VOUkVBQ0hBQkxFEAISLQopUlVOVElNRV9VTkFWQUlMQUJMRV9SRUFTT05fUkVNT1RFX1RJTUVPVVQQAxI2CjJSVU5USU1FX1VOQVZBSUxBQkxFX1JFQVNPTl9MT0NBTF9SRVBMSUNBX05PVF9SRUFEWRAEEjUKMVJVTlRJTUVfVU5BVkFJTEFCTEVfUkVBU09OX0NSRURFTlRJQUxfVU5BVkFJTEFCTEUQBRIwCixSVU5USU1FX1VOQVZBSUxBQkxFX1JFQVNPTl9XT1JLU1BBQ0VfT1BFTklORxAGEigKJFJVTlRJTUVfVU5BVkFJTEFCTEVfUkVBU09OX1BFRVJfQlVTWRAHKvABChRSdW50aW1lUmVmdXNhbFJlYXNvbhImCiJSVU5USU1FX1JFRlVTQUxfUkVBU09OX1VOU1BFQ0lGSUVEEAASLQopUlVOVElNRV9SRUZVU0FMX1JFQVNPTl9JTlZBTElEX0NSRURFTlRJQUwQARIoCiRSVU5USU1FX1JFRlVTQUxfUkVBU09OX0FDQ0VTU19ERU5JRUQQAhIrCidSVU5USU1FX1JFRlVTQUxfUkVBU09OX1BMQUNFTUVOVF9QT0xJQ1kQAxIqCiZSVU5USU1FX1JFRlVTQUxfUkVBU09OX1NFU1NJT05fVU5CT1VORBAEKpUEChZGb2xsb3dPYnNlcnZhdGlvblBoYXNlEigKJEZPTExPV19PQlNFUlZBVElPTl9QSEFTRV9VTlNQRUNJRklFRBAAEiQKIEZPTExPV19PQlNFUlZBVElPTl9QSEFTRV9ERVNJUkVEEAESIAocRk9MTE9XX09CU0VSVkFUSU9OX1BIQVNFX0RVRRACEiQKIEZPTExPV19PQlNFUlZBVElPTl9QSEFTRV9SVU5OSU5HEAMSJAogRk9MTE9XX09CU0VSVkFUSU9OX1BIQVNFX1NFVFRMRUQQBBIwCixGT0xMT1dfT0JTRVJWQVRJT05fUEhBU0VfQVdBSVRJTkdfREVQRU5ERU5DWRAFEiQKIEZPTExPV19PQlNFUlZBVElPTl9QSEFTRV9PRkZMSU5FEAYSLworRk9MTE9XX09CU0VSVkFUSU9OX1BIQVNFX0lOVkFMSURfQ1JFREVOVElBTBAHEiQKIEZPTExPV19PQlNFUlZBVElPTl9QSEFTRV9CQUNLT0ZGEAgSLAooRk9MTE9XX09CU0VSVkFUSU9OX1BIQVNFX1JFQk9STl9SRU1PVEVMWRAJEiwKKEZPTExPV19PQlNFUlZBVElPTl9QSEFTRV9SRVBMQUNJTkdfTE9DQUwQChIyCi5GT0xMT1dfT0JTRVJWQVRJT05fUEhBU0VfTE9DQUxfQ09QWV9VTlJFQURBQkxFEAsqVwoKTGlua1N0YXR1cxIbChdMSU5LX1NUQVRVU19VTlNQRUNJRklFRBAAEhQKEExJTktfU1RBVFVTX09QRU4QARIWChJMSU5LX1NUQVRVU19DTE9TRUQQAip2CgxUcmFuc2ZlcktpbmQSHQoZVFJBTlNGRVJfS0lORF9VTlNQRUNJRklFRBAAEhcKE1RSQU5TRkVSX0tJTkRfREVMVEEQARIWChJUUkFOU0ZFUl9LSU5EX0ZVTEwQAhIWChJUUkFOU0ZFUl9LSU5EX1BVU0gQA2IGcHJvdG8z", [file_google_protobuf_any, file_nomos_kernel_v1_progress, file_nomos_runtime_v2_correspondence_desk, file_nomos_runtime_v2_custody_desk, file_nomos_runtime_v2_placement_desk, file_nomos_runtime_v2_maintenance_desk, file_nomos_runtime_v2_model_adoption_desk, file_nomos_runtime_v2_receiving_group_desk, file_nomos_runtime_v2_recovery_desk]);
5999
5999
  var RuntimeFailureSchema = /* @__PURE__ */ messageDesc(file_nomos_runtime_v2_observability_desk, 1);
6000
6000
  var RuntimeUnavailableSchema = /* @__PURE__ */ messageDesc(file_nomos_runtime_v2_observability_desk, 2);
6001
6001
  var RuntimeRefusalSchema = /* @__PURE__ */ messageDesc(file_nomos_runtime_v2_observability_desk, 3);
@@ -6009,6 +6009,7 @@ var WatchChangedSchema = /* @__PURE__ */ messageDesc(file_nomos_runtime_v2_obser
6009
6009
  var SyncCompletedSchema = /* @__PURE__ */ messageDesc(file_nomos_runtime_v2_observability_desk, 11);
6010
6010
  var LinkChangedSchema = /* @__PURE__ */ messageDesc(file_nomos_runtime_v2_observability_desk, 12);
6011
6011
  var RuntimeSpanSchema = /* @__PURE__ */ messageDesc(file_nomos_runtime_v2_observability_desk, 15);
6012
+ var WritePreparationSchema = /* @__PURE__ */ messageDesc(file_nomos_runtime_v2_observability_desk, 18);
6012
6013
  var RuntimeFailureKindSchema = /* @__PURE__ */ enumDesc(file_nomos_runtime_v2_observability_desk, 0);
6013
6014
  var RuntimeFailureKind = /* @__PURE__ */ tsEnum(RuntimeFailureKindSchema);
6014
6015
  var RuntimeUnavailableReasonSchema = /* @__PURE__ */ enumDesc(file_nomos_runtime_v2_observability_desk, 1);
@@ -16356,6 +16357,18 @@ var CredentialSessions = class {
16356
16357
  // ../src/runtime/credential_recovery_desk.ts
16357
16358
  init_define_NOMOS_KERNEL_RELEASE();
16358
16359
 
16360
+ // ../src/runtime/credential_failure.ts
16361
+ init_define_NOMOS_KERNEL_RELEASE();
16362
+ function recoverableCredential(failure) {
16363
+ return failure.code === "nomos.auth.token_expired" || failure.code === "nomos.auth.token_missing";
16364
+ }
16365
+ function syncNeedsCredentialRefresh(result, failure) {
16366
+ if (failure !== void 0 && recoverableCredential(failure)) return true;
16367
+ const offer = result?.offerReceipt?.outcome;
16368
+ const pushNeedsRefresh = offer?.case === "failure" ? recoverableCredential(offer.value) : result?.pushError?.status === 401;
16369
+ return pushNeedsRefresh || result?.discoveryError?.status === 401;
16370
+ }
16371
+
16359
16372
  // ../src/runtime/credential_renewal.ts
16360
16373
  init_define_NOMOS_KERNEL_RELEASE();
16361
16374
  var RENEW_BEFORE_MILLIS = 5 * 6e4;
@@ -16621,7 +16634,7 @@ var CredentialRecoveryDesk = class {
16621
16634
  return false;
16622
16635
  }
16623
16636
  observeSync(session, event) {
16624
- if (!syncNeedsCredentialRefresh(event) || this.#tokenless(session)) return;
16637
+ if (!syncNeedsCredentialRefresh(event.result, event.failure) || this.#tokenless(session)) return;
16625
16638
  this.#prune();
16626
16639
  const capability = this.#ports.capabilityFor(session);
16627
16640
  const existing = this.#active.get(capability);
@@ -16823,15 +16836,6 @@ var CredentialRecoveryDesk = class {
16823
16836
  this.#completed.clear();
16824
16837
  }
16825
16838
  };
16826
- function recoverableCredential(failure) {
16827
- return failure.code === "nomos.auth.token_expired" || failure.code === "nomos.auth.token_missing";
16828
- }
16829
- function syncNeedsCredentialRefresh(event) {
16830
- const failures = [event.result?.pushError, event.result?.discoveryError];
16831
- if (failures.some((failure) => failure?.status === 401 || failure?.status === 403)) return true;
16832
- const text = `${event.failure?.message ?? ""} ${failures.map((failure) => failure?.message ?? "").join(" ")}`.toLowerCase();
16833
- return text.includes("unauthenticated") || text.includes("token expired") || text.includes("invalid token");
16834
- }
16835
16839
 
16836
16840
  // ../src/runtime/device_identity_desk.ts
16837
16841
  init_define_NOMOS_KERNEL_RELEASE();
@@ -17750,6 +17754,7 @@ var ReceivingGroupDesk = class {
17750
17754
  #accounts = /* @__PURE__ */ new Map();
17751
17755
  /** D22: device key hash → the workspaces the console reported this key enrolled in (this process's memory, not truth). */
17752
17756
  #consoleEnrolments = /* @__PURE__ */ new Map();
17757
+ #preparingWrites = /* @__PURE__ */ new Map();
17753
17758
  #sequence = 0;
17754
17759
  #internalRequestId = 30000000000n;
17755
17760
  constructor(ports) {
@@ -17789,16 +17794,124 @@ var ReceivingGroupDesk = class {
17789
17794
  if (head === account.observedHead) return;
17790
17795
  this.#tails.set(workspace, { ...account, inFlight: false, attempts: 0, notBefore: 0n });
17791
17796
  }
17792
- /** A refused nominal Home action can wait for framework recovery and retry once.
17793
- * Sealed/pending history is never re-signed, and no child is mistaken for Home. */
17797
+ /** Only a proven non-admission can enter recovery. Console-enrolled children
17798
+ * share the owning application's route, selected by credential capability and
17799
+ * principal, never by workspace-name guessing or an unverified payload. */
17794
17800
  recoverOffer(request, response) {
17795
- if (!this.#ports.call || request.command.case !== "kernelCall" || request.command.value.credentialUse !== CredentialUse.SESSION || request.command.value.request?.operation.case !== "offer" || request.command.value.request.operation.value.intent.case !== "call" || response.outcome.case !== "failure" || !["nomos.authz.signer_enrollment_missing", "nomos.authz.device_key_revoked"].includes(response.outcome.value.code)) return;
17801
+ if (!this.#ports.call || request.command.case !== "kernelCall" || request.command.value.credentialUse !== CredentialUse.SESSION || request.command.value.request?.operation.case !== "offer" || request.command.value.request.operation.value.intent.case !== "call") return;
17802
+ const missing = response.outcome.case === "failure" ? response.outcome.value.code === "nomos.authz.signer_enrollment_missing" : response.outcome.case === "offer" && response.outcome.value.outcome.case === "refused" && response.outcome.value.outcome.value.refusal?.code === KernelRefusalCode.FORGED_PRINCIPAL && response.outcome.value.outcome.value.refusal.details?.fields.actor?.kind.case === "text" && response.outcome.value.outcome.value.refusal.details.fields.actor.kind.value === "unenrolled-signer-key";
17803
+ const revoked = response.outcome.case === "failure" && response.outcome.value.code === "nomos.authz.device_key_revoked";
17804
+ if (!missing && !revoked) return;
17796
17805
  const binding = this.#ports.binding(request.command.value.session);
17797
- const account = binding ? this.#accounts.get(binding.workspace) : void 0;
17798
- if (!account) return;
17806
+ if (!binding) return;
17807
+ const enrolled = [...this.#accounts.values()].find((account2) => {
17808
+ const owner = this.#ports.binding(account2.session);
17809
+ return account2.enrolment && owner?.credentialCapability === binding.credentialCapability && owner.principal === binding.principal;
17810
+ });
17799
17811
  const copy3 = fromBinary(RuntimeRequestSchema, toBinary(RuntimeRequestSchema, request));
17812
+ if (enrolled?.enrolment) {
17813
+ if (revoked) return this.#writeRefused(binding.workspace, "This device's access has been revoked.");
17814
+ return this.#begin(request, `enrolled-write-${++this.#sequence}`, (workId2) => this.#prepareEnrolledWrite(copy3, binding, enrolled.application, enrolled.enrolment, workId2));
17815
+ }
17816
+ const account = this.#accounts.get(binding.workspace);
17817
+ if (!account) return;
17800
17818
  return this.#begin(request, `receiving-group-authoring-${++this.#sequence}`, (workId2) => this.#recoverOffer(copy3, response, account, workId2));
17801
17819
  }
17820
+ #writeRefused(workspace, detail, reason = RuntimeRefusalReason.ACCESS_DENIED) {
17821
+ return { case: "refused", value: create(RuntimeRefusalSchema, {
17822
+ reason,
17823
+ workspace,
17824
+ retryable: false,
17825
+ detail
17826
+ }) };
17827
+ }
17828
+ #writeWaiting(workspace, detail) {
17829
+ return { case: "unavailable", value: create(RuntimeUnavailableSchema, {
17830
+ reason: RuntimeUnavailableReason.CREDENTIAL_UNAVAILABLE,
17831
+ workspace,
17832
+ retryable: true,
17833
+ retryAfterMillis: 1e3,
17834
+ detail
17835
+ }) };
17836
+ }
17837
+ *#prepareEnrolledWrite(request, binding, application, route, workId2) {
17838
+ if (request.command.case !== "kernelCall") throw new Error("write preparation lost its call");
17839
+ const command = request.command.value;
17840
+ const principal = binding.principal;
17841
+ const unavailable = () => this.#writeWaiting(
17842
+ binding.workspace,
17843
+ "This device is still being prepared to save changes. Your change has not been saved. Try again when connected."
17844
+ );
17845
+ this.#ports.emit(binding.session, { case: "writePreparation", value: create(WritePreparationSchema, {
17846
+ requestId: command.request.requestId,
17847
+ workspace: binding.workspace
17848
+ }) });
17849
+ const key = JSON.stringify([binding.credentialCapability, principal, binding.workspace]);
17850
+ const shared = this.#preparingWrites.get(key);
17851
+ const deadline = this.#ports.clock.monotonicNanoseconds() + 30000000000n;
17852
+ let wake = 0;
17853
+ if (shared) {
17854
+ while (!shared.settled) {
17855
+ if (this.#ports.clock.monotonicNanoseconds() >= deadline) return unavailable();
17856
+ const receipt = yield { case: "wake", value: create(PeerWakeEffectSchema, {
17857
+ effectId: `${workId2}:join-${++wake}`,
17858
+ delayMillis: 250
17859
+ }) };
17860
+ if (receipt.outcome.case !== "woken") return unavailable();
17861
+ }
17862
+ if (shared.result) return shared.result;
17863
+ } else {
17864
+ const preparation = { settled: false };
17865
+ this.#preparingWrites.set(key, preparation);
17866
+ try {
17867
+ const secret = this.#ports.authorSecretIfPresent(binding.session);
17868
+ if (!secret) return preparation.result = this.#writeRefused(binding.workspace, "Sign in again to set up this device.", RuntimeRefusalReason.INVALID_CREDENTIAL);
17869
+ const keyHash = sha256Hex(this.#ports.deriveSigningPublicKey(secret));
17870
+ let posture = this.#ports.posture(request, binding.session, principal, secret, false);
17871
+ if (posture.deviceSignerStatus === DeviceSignerStatus.REVOKED) {
17872
+ return preparation.result = this.#writeRefused(binding.workspace, "This device's access has been revoked.");
17873
+ }
17874
+ if (!posture.enrolledDevice || posture.deviceSignerStatus !== DeviceSignerStatus.ACTIVE) {
17875
+ const enrolled = yield* this.#enrolAtConsole(
17876
+ route,
17877
+ binding,
17878
+ `${application}|${principal}`,
17879
+ principal,
17880
+ keyHash,
17881
+ [binding.workspace],
17882
+ `${workId2}:console`
17883
+ );
17884
+ if (enrolled.case === "deferred") return preparation.result = enrolled.credentialRefused ? this.#writeRefused(binding.workspace, "Sign in again to set up this device.", RuntimeRefusalReason.INVALID_CREDENTIAL) : unavailable();
17885
+ if (!(binding.workspace in enrolled.value.heads)) {
17886
+ return preparation.result = this.#writeRefused(binding.workspace, "This device could not obtain access to save changes in this workspace.");
17887
+ }
17888
+ while (true) {
17889
+ posture = this.#ports.posture(request, binding.session, principal, secret, false);
17890
+ if (posture.deviceSignerStatus === DeviceSignerStatus.REVOKED) {
17891
+ return preparation.result = this.#writeRefused(binding.workspace, "This device's access has been revoked.");
17892
+ }
17893
+ if (posture.enrolledDevice && posture.deviceSignerStatus === DeviceSignerStatus.ACTIVE) break;
17894
+ if (this.#ports.clock.monotonicNanoseconds() >= deadline) return preparation.result = unavailable();
17895
+ const receipt = yield { case: "wake", value: create(PeerWakeEffectSchema, {
17896
+ effectId: `${workId2}:observe-${++wake}`,
17897
+ delayMillis: 500
17898
+ }) };
17899
+ if (receipt.outcome.case !== "woken") return preparation.result = unavailable();
17900
+ }
17901
+ }
17902
+ } catch (error) {
17903
+ if (error instanceof ReceivingGroupRefusal) {
17904
+ return preparation.result = this.#writeRefused(binding.workspace, "This device could not obtain access to save changes in this workspace.");
17905
+ }
17906
+ preparation.result = unavailable();
17907
+ throw error;
17908
+ } finally {
17909
+ preparation.settled = true;
17910
+ this.#preparingWrites.delete(key);
17911
+ }
17912
+ }
17913
+ return { case: "retried", request, response: this.#ports.call(request, command) };
17914
+ }
17802
17915
  *#recoverOffer(request, response, account, workId2) {
17803
17916
  const workspace = this.#ports.binding(account.session).workspace;
17804
17917
  const deadline = this.#ports.clock.monotonicNanoseconds() + 60000000000n;
@@ -17866,6 +17979,7 @@ var ReceivingGroupDesk = class {
17866
17979
  this.#opening.clear();
17867
17980
  this.#accounts.clear();
17868
17981
  this.#consoleEnrolments.clear();
17982
+ this.#preparingWrites.clear();
17869
17983
  }
17870
17984
  #begin(request, workId2, procedure) {
17871
17985
  const held = {
@@ -21821,12 +21935,12 @@ var ReplicaFollowController = class {
21821
21935
  this.#desk.transition(result.workspace, { case: ReplicaFollowEventKind.WentOffline, nowUnixMillis });
21822
21936
  return delayUntilDue(this.#desk.get(result.workspace), nowUnixMillis);
21823
21937
  }
21824
- const credentialFailure = [result.pushError, result.discoveryError].find((failure) => failure?.status === 401 || failure?.status === 403);
21825
- if (credentialFailure) {
21938
+ if (syncNeedsCredentialRefresh(result)) {
21939
+ const offer = result.offerReceipt?.outcome;
21826
21940
  const transition = this.#desk.transition(result.workspace, {
21827
21941
  case: ReplicaFollowEventKind.CredentialRejected,
21828
21942
  nowUnixMillis,
21829
- failure: credentialFailure.message || `remote rejected credential with HTTP ${credentialFailure.status}`
21943
+ failure: (offer?.case === "failure" ? offer.value.message : void 0) || result.pushError?.message || result.discoveryError?.message || "remote requires a refreshed credential"
21830
21944
  });
21831
21945
  if (transition.effects.some((effect) => effect.case === ReplicaFollowEffectKind.RefreshCredential)) {
21832
21946
  return 0;
@@ -30152,6 +30266,7 @@ var PeerOffice = class {
30152
30266
  return this.#responses.modelAdoption(request, step);
30153
30267
  }
30154
30268
  #receivingGroupResponse(request, step) {
30269
+ if (step.case === "refused") return this.#response(request, { case: "refused", value: step.value });
30155
30270
  if (step.case === "retried") return this.#birthCustodyResponse(step.request, this.#birthCustody.afterKernelCall(step.request, step.response));
30156
30271
  if (step.case === "pending") return this.#response(request, { case: "correspondencePending", value: step.value });
30157
30272
  if (step.case === "failure") return this.#failure(request, step.code, step.message);
@@ -7,7 +7,6 @@ import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
7
7
  import 'package:nomos_types/nomos_types.dart'
8
8
  show
9
9
  NomosErrorInfo,
10
- NomosRefusalReason,
11
10
  NomosRefused,
12
11
  NomosRuntimeFailure,
13
12
  NomosRuntimeFailureKind,
@@ -46,27 +45,7 @@ final class NmsRuntimeV2Gateway {
46
45
  throw NomosUnavailable.fromRuntime(response.unavailable);
47
46
  }
48
47
  if (response.hasRefused()) {
49
- final refused = response.refused;
50
- throw NomosRefused(
51
- reason: switch (refused.reason) {
52
- nomos_protocol.RuntimeRefusalReason
53
- .RUNTIME_REFUSAL_REASON_INVALID_CREDENTIAL =>
54
- NomosRefusalReason.invalidCredential,
55
- nomos_protocol
56
- .RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_ACCESS_DENIED =>
57
- NomosRefusalReason.accessDenied,
58
- nomos_protocol
59
- .RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_PLACEMENT_POLICY =>
60
- NomosRefusalReason.placementPolicy,
61
- nomos_protocol
62
- .RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_SESSION_UNBOUND =>
63
- NomosRefusalReason.sessionUnbound,
64
- _ => NomosRefusalReason.unknown,
65
- },
66
- workspace: refused.workspace,
67
- retryable: refused.retryable,
68
- detail: refused.detail,
69
- );
48
+ throw NomosRefused.fromRuntime(response.refused);
70
49
  }
71
50
  if (response.hasFailure()) {
72
51
  final failure = response.failure;
@@ -16,7 +16,8 @@ import 'package:nomos_peer_host/nomos_peer_host.dart'
16
16
  NomosProviderCredentialStore,
17
17
  NomosSignIn;
18
18
  import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
19
- import 'package:nomos_types/nomos_types.dart' show NomosUnavailable;
19
+ import 'package:nomos_types/nomos_types.dart'
20
+ show NomosUnavailable, NomosRefused;
20
21
 
21
22
  import 'console_enrolment.dart';
22
23
  import 'console_http_stub.dart' if (dart.library.io) 'console_http_io.dart'
@@ -648,6 +649,9 @@ mixin NomosRuntimeV2KernelCalls
648
649
  if (response.hasUnavailable()) {
649
650
  throw NomosUnavailable.fromRuntime(response.unavailable);
650
651
  }
652
+ if (response.hasRefused()) {
653
+ throw NomosRefused.fromRuntime(response.refused);
654
+ }
651
655
  if (!response.hasKernelCall()) {
652
656
  throw StateError(
653
657
  'Runtime v2 returned ${response.whichOutcome().name} for a kernel call',
@@ -0,0 +1,48 @@
1
+ import 'dart:async';
2
+
3
+ /// Presentation of one application action. Completion is the action's local
4
+ /// result; remote durability remains on its existing operation receipt.
5
+ enum NomosWritePhase { running, preparing, completed, failed }
6
+
7
+ final class NomosWriteProgress {
8
+ const NomosWriteProgress(this.phase);
9
+ final NomosWritePhase phase;
10
+ }
11
+
12
+ /// Optional observation around a generated business action, including any
13
+ /// prerequisite calls it makes. Ordinary unobserved calls recover identically.
14
+ /// This holds no queue, credentials, authority or retry policy. It does not
15
+ /// persist a pending action across process death or cancel an accepted write.
16
+ abstract final class NomosWrite {
17
+ static final Object _observerKey = Object();
18
+
19
+ static Future<T> observe<T>(
20
+ Future<T> Function() action, {
21
+ required void Function(NomosWriteProgress) onProgress,
22
+ }) async {
23
+ void report(NomosWritePhase phase) {
24
+ try {
25
+ onProgress(NomosWriteProgress(phase));
26
+ } catch (_) {
27
+ // Presentation cannot change the operation's outcome.
28
+ }
29
+ }
30
+
31
+ report(NomosWritePhase.running);
32
+ try {
33
+ final result = await runZoned(action, zoneValues: {
34
+ _observerKey: (NomosWriteProgress progress) => report(progress.phase),
35
+ });
36
+ report(NomosWritePhase.completed);
37
+ return result;
38
+ } catch (_) {
39
+ report(NomosWritePhase.failed);
40
+ rethrow;
41
+ }
42
+ }
43
+
44
+ /// Framework projection seam. Capture before dispatch so an event delivered
45
+ /// outside the calling zone still reaches only its own action's observer.
46
+ static void Function(NomosWriteProgress)? captureObserver() =>
47
+ Zone.current[_observerKey] as void Function(NomosWriteProgress)?;
48
+ }
package/dart/pubspec.lock CHANGED
@@ -222,7 +222,7 @@ packages:
222
222
  path: "../dart_types"
223
223
  relative: true
224
224
  source: path
225
- version: "0.11.2"
225
+ version: "0.11.3"
226
226
  package_config:
227
227
  dependency: "direct main"
228
228
  description:
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.66.7
10
+ version: 0.66.9
11
11
 
12
12
  environment:
13
13
  sdk: ^3.5.0
@@ -18,7 +18,7 @@ dependencies:
18
18
  # The pure-data core (wire types, offer codec, NomosReads, schema validation, error
19
19
  # info). nomos_client depends on and re-exports it, so existing imports are unchanged.
20
20
  # For local monorepo dev, pubspec_overrides.yaml points this at ../dart_types.
21
- nomos_types: ^0.11.2
21
+ nomos_types: ^0.11.3
22
22
  fixnum: ^1.1.1
23
23
  json_schema: ^5.2.2
24
24
  crypto: ^3.0.0
@@ -3,6 +3,7 @@ import 'dart:convert';
3
3
  import 'dart:typed_data';
4
4
 
5
5
  import 'package:nomos_client/src/nms_generated_runtime.dart';
6
+ import 'package:nomos_client/src/write_progress.dart';
6
7
  import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
7
8
  import 'package:test/test.dart';
8
9
 
@@ -22,6 +23,7 @@ final class _RepairTransport
22
23
  var syncCalls = 0;
23
24
  final syncSessions = <String>[];
24
25
  var offerCalls = 0;
26
+ Completer<void>? holdOffer;
25
27
  var expireNextSessionOffer = false;
26
28
  var refuseSignerEnrollmentOffers = 0;
27
29
  var refuseKernelSignerOffers = 0;
@@ -142,6 +144,14 @@ final class _RepairTransport
142
144
  }
143
145
  if (request.hasOffer()) {
144
146
  offerCalls += 1;
147
+ if (holdOffer != null) {
148
+ pushRuntime(protocol.RuntimeEvent(
149
+ writePreparation: protocol.WritePreparation(
150
+ requestId: request.requestId,
151
+ workspace: session,
152
+ )));
153
+ await holdOffer!.future;
154
+ }
145
155
  if (refuseKernelSignerOffers > 0) {
146
156
  refuseKernelSignerOffers -= 1;
147
157
  return protocol.CallResponse(
@@ -225,6 +235,42 @@ Future<DartReception> _connected(_RepairTransport transport) async {
225
235
  }
226
236
 
227
237
  void main() {
238
+ test('runtime preparation reaches only its pending generated action observer',
239
+ () async {
240
+ final transport = _RepairTransport()..holdOffer = Completer<void>();
241
+ final bridge = await _connected(transport);
242
+ addTearDown(bridge.dispose);
243
+ final phases = <NomosWritePhase>[];
244
+ final pending = NomosWrite.observe(
245
+ () => bridge
246
+ .session('estate-1')
247
+ .offerGeneratedCallOutcome(call: _estateCall()),
248
+ onProgress: (progress) => phases.add(progress.phase));
249
+ await Future<void>.delayed(Duration.zero);
250
+ expect(phases, [NomosWritePhase.running, NomosWritePhase.preparing]);
251
+ transport.pushRuntime(protocol.RuntimeEvent(
252
+ writePreparation: protocol.WritePreparation(
253
+ requestId: 'another-action',
254
+ workspace: 'estate-1',
255
+ )));
256
+ await Future<void>.delayed(Duration.zero);
257
+ expect(phases.length, 2);
258
+ final requestId = transport.lastCall!.requestId;
259
+ transport.holdOffer!.complete();
260
+ await pending;
261
+ expect(phases.last, NomosWritePhase.completed);
262
+ expect(transport.offerCalls, 1);
263
+ final completed = List<NomosWritePhase>.of(phases);
264
+ transport.pushRuntime(protocol.RuntimeEvent(
265
+ writePreparation: protocol.WritePreparation(
266
+ requestId: requestId,
267
+ workspace: 'estate-1',
268
+ )));
269
+ await Future<void>.delayed(Duration.zero);
270
+ expect(phases, completed,
271
+ reason: 'completed operations detach their observers');
272
+ });
273
+
228
274
  test('kernel unenrolled signer refusal repairs the child and retries once',
229
275
  () async {
230
276
  final transport = _RepairTransport()..refuseKernelSignerOffers = 1;
@@ -30,6 +30,35 @@ final class _ScriptedKernelCalls extends _ScriptedRuntime
30
30
  }
31
31
 
32
32
  void main() {
33
+ test('a refused generated write preserves typed sign-in and access outcomes',
34
+ () async {
35
+ for (final reason in [
36
+ protocol.RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_INVALID_CREDENTIAL,
37
+ protocol.RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_ACCESS_DENIED,
38
+ ]) {
39
+ final runtime =
40
+ _ScriptedKernelCalls((request) => protocol.RuntimeResponse(
41
+ protocolVersion: 2,
42
+ requestId: request.requestId,
43
+ refused: protocol.RuntimeRefusal(
44
+ reason: reason, workspace: 'estate', retryable: false),
45
+ ));
46
+ await expectLater(
47
+ runtime.callKernel(
48
+ session: 'estate',
49
+ request: protocol.CallRequest(),
50
+ credentialUse: protocol.CredentialUse.CREDENTIAL_USE_SESSION),
51
+ throwsA(isA<NomosRefused>().having(
52
+ (e) => e.reason,
53
+ 'reason',
54
+ reason ==
55
+ protocol.RuntimeRefusalReason
56
+ .RUNTIME_REFUSAL_REASON_INVALID_CREDENTIAL
57
+ ? NomosRefusalReason.invalidCredential
58
+ : NomosRefusalReason.accessDenied)));
59
+ }
60
+ });
61
+
33
62
  test('a kernel call on a copy not ready yet is the typed wait reads sit out',
34
63
  () async {
35
64
  // A fresh device's first read of a workspace it is still fetching (node, web and in-app
@@ -0,0 +1,56 @@
1
+ import 'dart:async';
2
+ import 'package:nomos_client/src/write_progress.dart';
3
+ import 'package:test/test.dart';
4
+
5
+ void main() {
6
+ test('observation spans prerequisites and preserves the result', () async {
7
+ final phases = <NomosWritePhase>[];
8
+ final result = await NomosWrite.observe(() async {
9
+ NomosWrite.captureObserver()!(
10
+ const NomosWriteProgress(NomosWritePhase.preparing));
11
+ await Future<void>.delayed(Duration.zero);
12
+ return 42;
13
+ }, onProgress: (p) => phases.add(p.phase));
14
+ expect(result, 42);
15
+ expect(phases, [
16
+ NomosWritePhase.running,
17
+ NomosWritePhase.preparing,
18
+ NomosWritePhase.completed
19
+ ]);
20
+ expect(NomosWrite.captureObserver(), isNull);
21
+ });
22
+ test('concurrent observers do not receive each other\'s progress', () async {
23
+ final first = <NomosWritePhase>[], second = <NomosWritePhase>[];
24
+ final gate = Completer<void>();
25
+ final one = NomosWrite.observe(() async {
26
+ final observer = NomosWrite.captureObserver()!;
27
+ await gate.future;
28
+ observer(const NomosWriteProgress(NomosWritePhase.preparing));
29
+ }, onProgress: (p) => first.add(p.phase));
30
+ await NomosWrite.observe(() async {},
31
+ onProgress: (p) => second.add(p.phase));
32
+ gate.complete();
33
+ await one;
34
+ expect(first, contains(NomosWritePhase.preparing));
35
+ expect(second, [NomosWritePhase.running, NomosWritePhase.completed]);
36
+ });
37
+ test('observer exceptions cannot reject or retry an action', () async {
38
+ var calls = 0;
39
+ expect(
40
+ await NomosWrite.observe(() async {
41
+ calls++;
42
+ return 7;
43
+ }, onProgress: (_) => throw StateError('widget gone')),
44
+ 7);
45
+ expect(calls, 1);
46
+ });
47
+ test('typed failure is preserved without retry', () async {
48
+ final failure = StateError('refused');
49
+ final phases = <NomosWritePhase>[];
50
+ await expectLater(
51
+ NomosWrite.observe(() async => throw failure,
52
+ onProgress: (p) => phases.add(p.phase)),
53
+ throwsA(same(failure)));
54
+ expect(phases, [NomosWritePhase.running, NomosWritePhase.failed]);
55
+ });
56
+ }
@@ -1,3 +1,7 @@
1
+ ## 0.11.3
2
+
3
+ - Add the optional per-call write preparation observation.
4
+
1
5
  ## 0.11.2
2
6
 
3
7
  - Carry the current generated kernel and Runtime v2 Protobuf contracts used by the
@@ -617,6 +617,7 @@ enum RuntimeEvent_Event {
617
617
  localCopyPreserved,
618
618
  foreignPlacementIgnored,
619
619
  bindingQuarantined,
620
+ writePreparation,
620
621
  notSet
621
622
  }
622
623
 
@@ -644,6 +645,7 @@ class RuntimeEvent extends $pb.GeneratedMessage {
644
645
  $5.PreservedLocalCopy? localCopyPreserved,
645
646
  $6.ForeignPlacementIgnored? foreignPlacementIgnored,
646
647
  $6.QuarantinedBinding? bindingQuarantined,
648
+ WritePreparation? writePreparation,
647
649
  }) {
648
650
  final result = create();
649
651
  if (sequence != null) result.sequence = sequence;
@@ -675,6 +677,7 @@ class RuntimeEvent extends $pb.GeneratedMessage {
675
677
  result.foreignPlacementIgnored = foreignPlacementIgnored;
676
678
  if (bindingQuarantined != null)
677
679
  result.bindingQuarantined = bindingQuarantined;
680
+ if (writePreparation != null) result.writePreparation = writePreparation;
678
681
  return result;
679
682
  }
680
683
 
@@ -709,6 +712,7 @@ class RuntimeEvent extends $pb.GeneratedMessage {
709
712
  27: RuntimeEvent_Event.localCopyPreserved,
710
713
  28: RuntimeEvent_Event.foreignPlacementIgnored,
711
714
  29: RuntimeEvent_Event.bindingQuarantined,
715
+ 30: RuntimeEvent_Event.writePreparation,
712
716
  0: RuntimeEvent_Event.notSet
713
717
  };
714
718
  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
@@ -736,7 +740,8 @@ class RuntimeEvent extends $pb.GeneratedMessage {
736
740
  26,
737
741
  27,
738
742
  28,
739
- 29
743
+ 29,
744
+ 30
740
745
  ])
741
746
  ..a<$fixnum.Int64>(
742
747
  1, _omitFieldNames ? '' : 'sequence', $pb.PbFieldType.OU6,
@@ -789,6 +794,8 @@ class RuntimeEvent extends $pb.GeneratedMessage {
789
794
  ..aOM<$6.QuarantinedBinding>(
790
795
  29, _omitFieldNames ? '' : 'bindingQuarantined',
791
796
  subBuilder: $6.QuarantinedBinding.create)
797
+ ..aOM<WritePreparation>(30, _omitFieldNames ? '' : 'writePreparation',
798
+ subBuilder: WritePreparation.create)
792
799
  ..hasRequiredFields = false;
793
800
 
794
801
  @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@@ -830,6 +837,7 @@ class RuntimeEvent extends $pb.GeneratedMessage {
830
837
  @$pb.TagNumber(27)
831
838
  @$pb.TagNumber(28)
832
839
  @$pb.TagNumber(29)
840
+ @$pb.TagNumber(30)
833
841
  RuntimeEvent_Event whichEvent() => _RuntimeEvent_EventByTag[$_whichOneof(0)]!;
834
842
  @$pb.TagNumber(10)
835
843
  @$pb.TagNumber(11)
@@ -851,6 +859,7 @@ class RuntimeEvent extends $pb.GeneratedMessage {
851
859
  @$pb.TagNumber(27)
852
860
  @$pb.TagNumber(28)
853
861
  @$pb.TagNumber(29)
862
+ @$pb.TagNumber(30)
854
863
  void clearEvent() => $_clearField($_whichOneof(0));
855
864
 
856
865
  @$pb.TagNumber(1)
@@ -1099,6 +1108,17 @@ class RuntimeEvent extends $pb.GeneratedMessage {
1099
1108
  void clearBindingQuarantined() => $_clearField(29);
1100
1109
  @$pb.TagNumber(29)
1101
1110
  $6.QuarantinedBinding ensureBindingQuarantined() => $_ensure(21);
1111
+
1112
+ @$pb.TagNumber(30)
1113
+ WritePreparation get writePreparation => $_getN(22);
1114
+ @$pb.TagNumber(30)
1115
+ set writePreparation(WritePreparation value) => $_setField(30, value);
1116
+ @$pb.TagNumber(30)
1117
+ $core.bool hasWritePreparation() => $_has(22);
1118
+ @$pb.TagNumber(30)
1119
+ void clearWritePreparation() => $_clearField(30);
1120
+ @$pb.TagNumber(30)
1121
+ WritePreparation ensureWritePreparation() => $_ensure(22);
1102
1122
  }
1103
1123
 
1104
1124
  /// Application-wide (no session): the replica follows this peer keeps up with in the background.
@@ -2225,6 +2245,75 @@ class TransferProgress extends $pb.GeneratedMessage {
2225
2245
  void clearKind() => $_clearField(3);
2226
2246
  }
2227
2247
 
2248
+ /// Transient observation for one refused, not-yet-admitted generated call. The
2249
+ /// office owns recovery; observing this event is never required for correctness.
2250
+ class WritePreparation extends $pb.GeneratedMessage {
2251
+ factory WritePreparation({
2252
+ $core.String? requestId,
2253
+ $core.String? workspace,
2254
+ }) {
2255
+ final result = create();
2256
+ if (requestId != null) result.requestId = requestId;
2257
+ if (workspace != null) result.workspace = workspace;
2258
+ return result;
2259
+ }
2260
+
2261
+ WritePreparation._();
2262
+
2263
+ factory WritePreparation.fromBuffer($core.List<$core.int> data,
2264
+ [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
2265
+ create()..mergeFromBuffer(data, registry);
2266
+ factory WritePreparation.fromJson($core.String json,
2267
+ [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
2268
+ create()..mergeFromJson(json, registry);
2269
+
2270
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
2271
+ _omitMessageNames ? '' : 'WritePreparation',
2272
+ package:
2273
+ const $pb.PackageName(_omitMessageNames ? '' : 'nomos.runtime.v2'),
2274
+ createEmptyInstance: create)
2275
+ ..aOS(1, _omitFieldNames ? '' : 'requestId')
2276
+ ..aOS(2, _omitFieldNames ? '' : 'workspace')
2277
+ ..hasRequiredFields = false;
2278
+
2279
+ @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
2280
+ WritePreparation clone() => deepCopy();
2281
+ @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
2282
+ WritePreparation copyWith(void Function(WritePreparation) updates) =>
2283
+ super.copyWith((message) => updates(message as WritePreparation))
2284
+ as WritePreparation;
2285
+
2286
+ @$core.override
2287
+ $pb.BuilderInfo get info_ => _i;
2288
+
2289
+ @$core.pragma('dart2js:noInline')
2290
+ static WritePreparation create() => WritePreparation._();
2291
+ @$core.override
2292
+ WritePreparation createEmptyInstance() => create();
2293
+ @$core.pragma('dart2js:noInline')
2294
+ static WritePreparation getDefault() => _defaultInstance ??=
2295
+ $pb.GeneratedMessage.$_defaultFor<WritePreparation>(create);
2296
+ static WritePreparation? _defaultInstance;
2297
+
2298
+ @$pb.TagNumber(1)
2299
+ $core.String get requestId => $_getSZ(0);
2300
+ @$pb.TagNumber(1)
2301
+ set requestId($core.String value) => $_setString(0, value);
2302
+ @$pb.TagNumber(1)
2303
+ $core.bool hasRequestId() => $_has(0);
2304
+ @$pb.TagNumber(1)
2305
+ void clearRequestId() => $_clearField(1);
2306
+
2307
+ @$pb.TagNumber(2)
2308
+ $core.String get workspace => $_getSZ(1);
2309
+ @$pb.TagNumber(2)
2310
+ set workspace($core.String value) => $_setString(1, value);
2311
+ @$pb.TagNumber(2)
2312
+ $core.bool hasWorkspace() => $_has(1);
2313
+ @$pb.TagNumber(2)
2314
+ void clearWorkspace() => $_clearField(2);
2315
+ }
2316
+
2228
2317
  const $core.bool _omitFieldNames =
2229
2318
  $core.bool.fromEnvironment('protobuf.omit_field_names');
2230
2319
  const $core.bool _omitMessageNames =
@@ -6,9 +6,9 @@
6
6
  library;
7
7
 
8
8
  import 'proto/nomos/runtime/v2/observability_desk.pb.dart'
9
- show RuntimeUnavailable;
9
+ show RuntimeUnavailable, RuntimeRefusal;
10
10
  import 'proto/nomos/runtime/v2/observability_desk.pbenum.dart'
11
- show RuntimeUnavailableReason;
11
+ show RuntimeUnavailableReason, RuntimeRefusalReason;
12
12
 
13
13
  enum NomosUnavailableReason {
14
14
  remoteOnlyOffline,
@@ -39,35 +39,33 @@ final class NomosUnavailable implements Exception {
39
39
  final String detail;
40
40
 
41
41
  /// The one translation of the generated `unavailable` arm, for every transport.
42
- factory NomosUnavailable.fromRuntime(RuntimeUnavailable unavailable) =>
43
- NomosUnavailable(
44
- reason: switch (unavailable.reason) {
45
- RuntimeUnavailableReason
46
- .RUNTIME_UNAVAILABLE_REASON_REMOTE_ONLY_OFFLINE =>
47
- NomosUnavailableReason.remoteOnlyOffline,
48
- RuntimeUnavailableReason
49
- .RUNTIME_UNAVAILABLE_REASON_REMOTE_UNREACHABLE =>
50
- NomosUnavailableReason.remoteUnreachable,
51
- RuntimeUnavailableReason.RUNTIME_UNAVAILABLE_REASON_REMOTE_TIMEOUT =>
52
- NomosUnavailableReason.remoteTimeout,
53
- RuntimeUnavailableReason
54
- .RUNTIME_UNAVAILABLE_REASON_LOCAL_REPLICA_NOT_READY =>
55
- NomosUnavailableReason.localReplicaNotReady,
56
- RuntimeUnavailableReason
57
- .RUNTIME_UNAVAILABLE_REASON_CREDENTIAL_UNAVAILABLE =>
58
- NomosUnavailableReason.credentialUnavailable,
59
- RuntimeUnavailableReason
60
- .RUNTIME_UNAVAILABLE_REASON_WORKSPACE_OPENING =>
61
- NomosUnavailableReason.workspaceOpening,
62
- _ => NomosUnavailableReason.unknown,
63
- },
64
- workspace: unavailable.workspace,
65
- retryable: unavailable.retryable,
66
- retryAfter: unavailable.hasRetryAfterMillis()
67
- ? Duration(milliseconds: unavailable.retryAfterMillis)
68
- : null,
69
- detail: unavailable.detail,
70
- );
42
+ factory NomosUnavailable.fromRuntime(
43
+ RuntimeUnavailable unavailable,
44
+ ) => NomosUnavailable(
45
+ reason: switch (unavailable.reason) {
46
+ RuntimeUnavailableReason.RUNTIME_UNAVAILABLE_REASON_REMOTE_ONLY_OFFLINE =>
47
+ NomosUnavailableReason.remoteOnlyOffline,
48
+ RuntimeUnavailableReason.RUNTIME_UNAVAILABLE_REASON_REMOTE_UNREACHABLE =>
49
+ NomosUnavailableReason.remoteUnreachable,
50
+ RuntimeUnavailableReason.RUNTIME_UNAVAILABLE_REASON_REMOTE_TIMEOUT =>
51
+ NomosUnavailableReason.remoteTimeout,
52
+ RuntimeUnavailableReason
53
+ .RUNTIME_UNAVAILABLE_REASON_LOCAL_REPLICA_NOT_READY =>
54
+ NomosUnavailableReason.localReplicaNotReady,
55
+ RuntimeUnavailableReason
56
+ .RUNTIME_UNAVAILABLE_REASON_CREDENTIAL_UNAVAILABLE =>
57
+ NomosUnavailableReason.credentialUnavailable,
58
+ RuntimeUnavailableReason.RUNTIME_UNAVAILABLE_REASON_WORKSPACE_OPENING =>
59
+ NomosUnavailableReason.workspaceOpening,
60
+ _ => NomosUnavailableReason.unknown,
61
+ },
62
+ workspace: unavailable.workspace,
63
+ retryable: unavailable.retryable,
64
+ retryAfter: unavailable.hasRetryAfterMillis()
65
+ ? Duration(milliseconds: unavailable.retryAfterMillis)
66
+ : null,
67
+ detail: unavailable.detail,
68
+ );
71
69
 
72
70
  @override
73
71
  String toString() =>
@@ -91,6 +89,23 @@ final class NomosRefused implements Exception {
91
89
  this.detail = '',
92
90
  });
93
91
 
92
+ factory NomosRefused.fromRuntime(RuntimeRefusal refused) => NomosRefused(
93
+ reason: switch (refused.reason) {
94
+ RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_INVALID_CREDENTIAL =>
95
+ NomosRefusalReason.invalidCredential,
96
+ RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_ACCESS_DENIED =>
97
+ NomosRefusalReason.accessDenied,
98
+ RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_PLACEMENT_POLICY =>
99
+ NomosRefusalReason.placementPolicy,
100
+ RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_SESSION_UNBOUND =>
101
+ NomosRefusalReason.sessionUnbound,
102
+ _ => NomosRefusalReason.unknown,
103
+ },
104
+ workspace: refused.workspace,
105
+ retryable: refused.retryable,
106
+ detail: refused.detail,
107
+ );
108
+
94
109
  final NomosRefusalReason reason;
95
110
  final String workspace;
96
111
  final bool retryable;
@@ -9,7 +9,7 @@ description: >-
9
9
  Pure kernel wire/data types for Nomos Dart frontends — no transport, no bridge,
10
10
  no runtime. Typed protobuf and USDA boundary values plus the offer codec,
11
11
  reactive read engine, schema validation, and data-only error/trace types.
12
- version: 0.11.2
12
+ version: 0.11.3
13
13
 
14
14
  environment:
15
15
  sdk: ^3.5.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.111.0",
3
+ "version": "0.111.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
@@ -72,7 +72,7 @@
72
72
  "test": "vitest run"
73
73
  },
74
74
  "dependencies": {
75
- "@githolon/runtime": "^0.111.0",
75
+ "@githolon/runtime": "^0.111.1",
76
76
  "@bufbuild/protobuf": "^2.13.0",
77
77
  "@bufbuild/protoc-gen-es": "^2.13.0",
78
78
  "esbuild": "0.24.2",
@@ -1906,6 +1906,7 @@ export function generateDartApplication(
1906
1906
  return [
1907
1907
  `// AUTO-GENERATED by nomos-compile — the tenant application surface for "${domain}".`,
1908
1908
  `// Business facts in, business records out. Runtime lifecycle is deliberately absent.`,
1909
+ `export 'package:nomos_client/src/write_progress.dart' show NomosWrite, NomosWriteProgress, NomosWritePhase;`,
1909
1910
  ...((mod.workspaceInvariants ?? []).some(rule => rule.refusal) ? [
1910
1911
  `export 'package:nomos_types/nomos_types.dart' show AdmissionDecision, Admitted, DomainRefused, FrameworkRefused, UnsupportedRefusalContract, NomosDomainRefusalEvidence, KernelRefusalCode;`,
1911
1912
  ] : []),
@@ -2540,12 +2540,12 @@ const dartTypesImportFor = (generatedSource: string): string => {
2540
2540
  * just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
2541
2541
  // 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
2542
2542
  // the pendingConflicts read rows.
2543
- const NOMOS_TYPES_DEP = "^0.11.2";
2543
+ const NOMOS_TYPES_DEP = "^0.11.3";
2544
2544
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
2545
2545
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
2546
2546
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
2547
- const NOMOS_CLIENT_DEP = "^0.66.7";
2548
- const NOMOS_FLUTTER_DEP = "^0.65.9";
2547
+ const NOMOS_CLIENT_DEP = "^0.66.9";
2548
+ const NOMOS_FLUTTER_DEP = "^0.65.13";
2549
2549
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in the Dart and Flutter
2550
2550
  // package pubspecs — a release without this bump fails the suite, so tenants never
2551
2551
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -3854,6 +3854,7 @@ function emitDartPackages(
3854
3854
  "",
3855
3855
  `export 'package:${packages.types}/${genRel}/${businessSharedFile}';`,
3856
3856
  `export 'package:nomos_client/src/workspace_open_progress.dart' show ApplicationWorkspaceOpenProgress;`,
3857
+ `export 'package:nomos_client/src/write_progress.dart' show NomosWrite, NomosWriteProgress, NomosWritePhase;`,
3857
3858
  `export 'package:nomos_types/nomos_types.dart' show CustodyError, CustodyPhase, CustodySource, NomosRecommendedAction;`,
3858
3859
  ...tenantTypesExportKeys
3859
3860
  .map((file) => {