@githolon/dsl 0.110.0 → 0.110.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/assets/operation-contract.wasm +0 -0
- package/dart/CHANGELOG.md +4 -0
- package/dart/lib/src/reception/event_desk.dart +11 -8
- package/dart/lib/src/reception/progress_desk.dart +4 -3
- package/dart/lib/src/runner/node-entry.mjs +114 -71
- package/dart/lib/src/sync_status_projection.dart +3 -1
- package/dart/pubspec.yaml +1 -1
- package/dart/test/reception_event_desk_close_test.dart +16 -0
- package/package.json +2 -2
- package/src/codegen_call_proto.ts +4 -2
- package/src/codegen_dart.ts +4 -0
- package/src/compile_package_main.ts +29 -12
|
Binary file
|
package/dart/CHANGELOG.md
CHANGED
|
@@ -194,17 +194,20 @@ final class ReceptionEventDesk {
|
|
|
194
194
|
|
|
195
195
|
Future<void> close() async {
|
|
196
196
|
await _syncStatusChanges.cancel();
|
|
197
|
-
|
|
197
|
+
// Public broadcast streams can have paused UI listeners. Their close()
|
|
198
|
+
// futures wait for those listeners to resume, so signal done without
|
|
199
|
+
// letting an external subscriber hold reception teardown open.
|
|
200
|
+
if (!_copyChanges.isClosed) unawaited(_copyChanges.close());
|
|
198
201
|
await typedWatches.close();
|
|
199
|
-
if (!_sync.isClosed)
|
|
200
|
-
if (!_custody.isClosed)
|
|
201
|
-
if (!_localWorkRejected.isClosed)
|
|
202
|
+
if (!_sync.isClosed) unawaited(_sync.close());
|
|
203
|
+
if (!_custody.isClosed) unawaited(_custody.close());
|
|
204
|
+
if (!_localWorkRejected.isClosed) unawaited(_localWorkRejected.close());
|
|
202
205
|
if (!_receivingGroupConvergence.isClosed) {
|
|
203
|
-
|
|
206
|
+
unawaited(_receivingGroupConvergence.close());
|
|
204
207
|
}
|
|
205
|
-
if (!_modelAdoption.isClosed)
|
|
206
|
-
if (!_preserved.isClosed)
|
|
207
|
-
if (!_foreignPlacements.isClosed)
|
|
208
|
+
if (!_modelAdoption.isClosed) unawaited(_modelAdoption.close());
|
|
209
|
+
if (!_preserved.isClosed) unawaited(_preserved.close());
|
|
210
|
+
if (!_foreignPlacements.isClosed) unawaited(_foreignPlacements.close());
|
|
208
211
|
await progress.close();
|
|
209
212
|
await syncStatuses.close();
|
|
210
213
|
}
|
|
@@ -260,8 +260,9 @@ final class ReceptionProgressDesk {
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
Future<void> close() async {
|
|
263
|
-
|
|
264
|
-
if (!
|
|
265
|
-
if (!
|
|
263
|
+
// Observers may be paused while their widget tree is being replaced.
|
|
264
|
+
if (!_spans.isClosed) unawaited(_spans.close());
|
|
265
|
+
if (!_transfers.isClosed) unawaited(_transfers.close());
|
|
266
|
+
if (!_kernel.isClosed) unawaited(_kernel.close());
|
|
266
267
|
}
|
|
267
268
|
}
|
|
@@ -21835,6 +21835,15 @@ var ReplicaFollowController = class {
|
|
|
21835
21835
|
}
|
|
21836
21836
|
if (result.stillAhead || result.pushFailed || result.directOfferDeferred || result.reconnectRequired) {
|
|
21837
21837
|
if (result.directOfferDeferred) {
|
|
21838
|
+
if (!result.reconnectRequired) {
|
|
21839
|
+
this.#desk.transition(result.workspace, {
|
|
21840
|
+
case: ReplicaFollowEventKind.Failed,
|
|
21841
|
+
nowUnixMillis,
|
|
21842
|
+
failure: "remote workspace opening; retrying",
|
|
21843
|
+
retryWithinMillis: 5e3
|
|
21844
|
+
});
|
|
21845
|
+
return delayUntilDue(this.#desk.get(result.workspace), nowUnixMillis);
|
|
21846
|
+
}
|
|
21838
21847
|
this.#desk.transition(result.workspace, {
|
|
21839
21848
|
case: ReplicaFollowEventKind.DependencyDeferred,
|
|
21840
21849
|
nowUnixMillis,
|
|
@@ -23740,6 +23749,34 @@ function resolveCustodyDisagreement(kernel, mount, requestId, policy, verdict) {
|
|
|
23740
23749
|
init_define_NOMOS_KERNEL_RELEASE();
|
|
23741
23750
|
var CUSTODY_OFFER_MEDIA_TYPE = "application/vnd.nomos.custody-offer-batch+protobuf";
|
|
23742
23751
|
var CUSTODY_OFFER_RESULT_MEDIA_TYPE = "application/vnd.nomos.custody-offer-result+protobuf";
|
|
23752
|
+
function settleCustodyOfferExchange(awaiting, response) {
|
|
23753
|
+
if (response.status === 202 && response.contentType?.includes("application/json")) {
|
|
23754
|
+
try {
|
|
23755
|
+
const pending = JSON.parse(new TextDecoder().decode(response.body));
|
|
23756
|
+
if (pending?.retryable === true && typeof pending.preparing === "string") {
|
|
23757
|
+
return { case: "maintenance", kind: pending.preparing };
|
|
23758
|
+
}
|
|
23759
|
+
} catch {
|
|
23760
|
+
}
|
|
23761
|
+
}
|
|
23762
|
+
if (!response.contentType?.includes(CUSTODY_OFFER_RESULT_MEDIA_TYPE)) {
|
|
23763
|
+
return {
|
|
23764
|
+
case: "unexpected",
|
|
23765
|
+
status: response.status,
|
|
23766
|
+
...response.contentType === void 0 ? {} : { contentType: response.contentType }
|
|
23767
|
+
};
|
|
23768
|
+
}
|
|
23769
|
+
const verdict = decodeCustodyOffer(response.body, awaiting.requestId);
|
|
23770
|
+
const receipt = custodyOfferReceipt(awaiting.requestId, verdict);
|
|
23771
|
+
switch (verdict.case) {
|
|
23772
|
+
case "admission":
|
|
23773
|
+
return { case: "admission", value: verdict.value, receipt, status: response.status };
|
|
23774
|
+
case "failure":
|
|
23775
|
+
return { case: "failure", value: verdict.value, receipt, status: response.status };
|
|
23776
|
+
case "blocked":
|
|
23777
|
+
return { case: "blocked", value: verdict.value, receipt, status: response.status };
|
|
23778
|
+
}
|
|
23779
|
+
}
|
|
23743
23780
|
function encodeInlineCustodyOffer(args) {
|
|
23744
23781
|
return toBinary(CustodyOfferBatchRequestSchema, create(CustodyOfferBatchRequestSchema, {
|
|
23745
23782
|
protocolVersion: 2,
|
|
@@ -23866,6 +23903,7 @@ function summarizeCustodyAdmission(admission) {
|
|
|
23866
23903
|
var PLACEMENT_REF3 = "refs/nomos/placement";
|
|
23867
23904
|
var LAW_PRODUCTS_MEDIA_TYPE = "application/vnd.nomos.export-law-products-response+protobuf";
|
|
23868
23905
|
var WorkspaceSyncPhase = Object.freeze({
|
|
23906
|
+
Idle: "idle",
|
|
23869
23907
|
Probe: "probe",
|
|
23870
23908
|
Offer: "offer",
|
|
23871
23909
|
Lease: "lease",
|
|
@@ -23899,7 +23937,6 @@ var WorkspaceSyncWork = class {
|
|
|
23899
23937
|
#rediscover;
|
|
23900
23938
|
/** The route the directory's answer yields, recomputed once that answer is observed. */
|
|
23901
23939
|
#reroute;
|
|
23902
|
-
#placementFetch;
|
|
23903
23940
|
#discoveredFrontier;
|
|
23904
23941
|
#observePlacement;
|
|
23905
23942
|
/** Ask the directory for the provider's current placement frontier this round although a
|
|
@@ -23908,19 +23945,14 @@ var WorkspaceSyncWork = class {
|
|
|
23908
23945
|
#directoryAnswered;
|
|
23909
23946
|
#placementFetched;
|
|
23910
23947
|
/** A round which converged without leasing, finishing once placement alone is fetched. */
|
|
23911
|
-
#afterPlacement;
|
|
23912
23948
|
#leased = false;
|
|
23913
|
-
#lease;
|
|
23914
23949
|
#command;
|
|
23915
23950
|
#requestId;
|
|
23916
23951
|
#started;
|
|
23917
|
-
#
|
|
23952
|
+
#state = { phase: WorkspaceSyncPhase.Idle };
|
|
23918
23953
|
#localHead;
|
|
23919
23954
|
#offerHead;
|
|
23920
23955
|
#previousHead;
|
|
23921
|
-
#fetchStarted;
|
|
23922
|
-
#offerStarted;
|
|
23923
|
-
#offerRequestId;
|
|
23924
23956
|
#pushMillis = 0;
|
|
23925
23957
|
#pushBytes = 0n;
|
|
23926
23958
|
#desiredHead;
|
|
@@ -24015,7 +24047,7 @@ var WorkspaceSyncWork = class {
|
|
|
24015
24047
|
throw new Error(`replica '${this.#binding.replicaId}' has no custody remote`);
|
|
24016
24048
|
}
|
|
24017
24049
|
if (this.#rediscover || this.#recheckPlacement || !this.#route?.placementObserved && !this.#route?.placementFrontier && !this.#kernel.resolveRef(this.#mount, this.#requestId, PLACEMENT_REF3)) {
|
|
24018
|
-
this.#
|
|
24050
|
+
this.#state = { phase: WorkspaceSyncPhase.Discover };
|
|
24019
24051
|
return this.#remember({
|
|
24020
24052
|
case: "pending",
|
|
24021
24053
|
value: create(CorrespondencePendingSchema, {
|
|
@@ -24035,13 +24067,13 @@ var WorkspaceSyncWork = class {
|
|
|
24035
24067
|
#openRound() {
|
|
24036
24068
|
if (this.#receiveState || this.#replaceLocalHead !== void 0) return this.#leaseEffect();
|
|
24037
24069
|
if (!this.#localHead) return this.#leaseEffect();
|
|
24038
|
-
|
|
24070
|
+
const requestId = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}:frontier`;
|
|
24039
24071
|
return this.#beginOffer(encodeInlineCustodyOffer({
|
|
24040
24072
|
session: this.#binding.replicaId,
|
|
24041
|
-
requestId
|
|
24073
|
+
requestId,
|
|
24042
24074
|
observedHead: this.#localHead,
|
|
24043
24075
|
intents: []
|
|
24044
|
-
}), true);
|
|
24076
|
+
}), { case: "awaiting", requestId }, true);
|
|
24045
24077
|
}
|
|
24046
24078
|
#offerOrReconcile() {
|
|
24047
24079
|
if (this.#command.admit && this.#localHead && this.#localHead !== this.#previousHead) {
|
|
@@ -24062,13 +24094,13 @@ var WorkspaceSyncWork = class {
|
|
|
24062
24094
|
throw new Error(`kernel did not classify transport for intent '${unspecified.intentId}'`);
|
|
24063
24095
|
}
|
|
24064
24096
|
if (suffix.length > 0) {
|
|
24065
|
-
|
|
24066
|
-
|
|
24097
|
+
const requestId2 = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}`;
|
|
24098
|
+
const startedAt = this.#clock.monotonicNanoseconds();
|
|
24067
24099
|
this.#offerAfterConverge = true;
|
|
24068
24100
|
this.#rebaseFrom = this.#previousHead;
|
|
24069
24101
|
return this.#beginOffer(encodeInlineCustodyOffer({
|
|
24070
24102
|
session: this.#binding.replicaId,
|
|
24071
|
-
requestId:
|
|
24103
|
+
requestId: requestId2,
|
|
24072
24104
|
...this.#previousHead === void 0 ? {} : { baseHead: this.#previousHead },
|
|
24073
24105
|
observedHead: this.#localHead,
|
|
24074
24106
|
intents: suffix,
|
|
@@ -24079,17 +24111,17 @@ var WorkspaceSyncWork = class {
|
|
|
24079
24111
|
this.#previousHead
|
|
24080
24112
|
) : void 0,
|
|
24081
24113
|
fastForwardOnly: true
|
|
24082
|
-
}));
|
|
24114
|
+
}), { case: "awaiting", requestId: requestId2, startedAt });
|
|
24083
24115
|
}
|
|
24084
24116
|
}
|
|
24085
|
-
|
|
24117
|
+
const requestId = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}:reconcile`;
|
|
24086
24118
|
return this.#beginOffer(encodeInlineCustodyOffer({
|
|
24087
24119
|
session: this.#binding.replicaId,
|
|
24088
|
-
requestId
|
|
24120
|
+
requestId,
|
|
24089
24121
|
baseHead: this.#previousHead,
|
|
24090
24122
|
observedHead: this.#localHead,
|
|
24091
24123
|
intents: []
|
|
24092
|
-
}));
|
|
24124
|
+
}), { case: "awaiting", requestId });
|
|
24093
24125
|
}
|
|
24094
24126
|
/**
|
|
24095
24127
|
* NO COMMON BASE — a round that cannot converge, and must not pay for history to find out.
|
|
@@ -24165,18 +24197,18 @@ var WorkspaceSyncWork = class {
|
|
|
24165
24197
|
#pullBeforeOffer() {
|
|
24166
24198
|
this.#offerAfterConverge = true;
|
|
24167
24199
|
this.#rebaseFrom = this.#previousHead;
|
|
24168
|
-
|
|
24200
|
+
const requestId = `${this.#binding.replicaId}:${this.#requestId}:${this.workId}:pull-${this.#rebases}`;
|
|
24169
24201
|
return this.#beginOffer(encodeInlineCustodyOffer({
|
|
24170
24202
|
session: this.#binding.replicaId,
|
|
24171
|
-
requestId
|
|
24203
|
+
requestId,
|
|
24172
24204
|
...this.#previousHead === void 0 ? {} : { baseHead: this.#previousHead },
|
|
24173
24205
|
observedHead: this.#localHead,
|
|
24174
24206
|
intents: []
|
|
24175
|
-
}));
|
|
24207
|
+
}), { case: "awaiting", requestId });
|
|
24176
24208
|
}
|
|
24177
|
-
#leaseEffect() {
|
|
24209
|
+
#leaseEffect(afterPlacement) {
|
|
24178
24210
|
const token = this.#authToken();
|
|
24179
|
-
this.#
|
|
24211
|
+
this.#state = { phase: WorkspaceSyncPhase.Lease, ...afterPlacement ? { afterPlacement } : {} };
|
|
24180
24212
|
return this.#remember({
|
|
24181
24213
|
case: "pending",
|
|
24182
24214
|
value: create(CorrespondencePendingSchema, {
|
|
@@ -24205,35 +24237,38 @@ var WorkspaceSyncWork = class {
|
|
|
24205
24237
|
`workspace sync '${this.workId}' expected effect '${expected}', received '${receipt.effectId}'`
|
|
24206
24238
|
);
|
|
24207
24239
|
}
|
|
24240
|
+
const state = this.#state;
|
|
24208
24241
|
const previousReceipt = this.#lastReceiptEffectId;
|
|
24209
24242
|
this.#lastReceiptEffectId = receipt.effectId;
|
|
24210
24243
|
try {
|
|
24211
24244
|
if (receipt.outcome.case === "failure") {
|
|
24212
|
-
if (
|
|
24245
|
+
if (state.phase === WorkspaceSyncPhase.Discover) return this.#openRound();
|
|
24213
24246
|
return this.#transportFailure(
|
|
24214
24247
|
receipt.outcome.value.message,
|
|
24215
24248
|
receipt.outcome.value.reason === PeerEffectFailureReason.OFFLINE
|
|
24216
24249
|
);
|
|
24217
24250
|
}
|
|
24218
|
-
if (
|
|
24219
|
-
|
|
24220
|
-
|
|
24221
|
-
if (
|
|
24222
|
-
|
|
24223
|
-
|
|
24251
|
+
if (state.phase === WorkspaceSyncPhase.Offer || state.phase === WorkspaceSyncPhase.Probe) {
|
|
24252
|
+
return this.#acceptOffer(receipt, state);
|
|
24253
|
+
}
|
|
24254
|
+
if (state.phase === WorkspaceSyncPhase.Lease) return this.#acceptLease(receipt, state);
|
|
24255
|
+
if (state.phase === WorkspaceSyncPhase.Discover) return this.#acceptDiscovery(receipt);
|
|
24256
|
+
if (state.phase === WorkspaceSyncPhase.Placement) {
|
|
24257
|
+
state.transfer.accept(receipt);
|
|
24224
24258
|
this.#placementFetched?.();
|
|
24225
|
-
if (
|
|
24226
|
-
return this.#beginMainFetch(
|
|
24259
|
+
if (state.afterPlacement) return this.#finish(state.afterPlacement);
|
|
24260
|
+
return this.#beginMainFetch(state.lease);
|
|
24227
24261
|
}
|
|
24228
|
-
if (
|
|
24262
|
+
if (state.phase === WorkspaceSyncPhase.Fetch) return this.#acceptFetch(receipt, state);
|
|
24229
24263
|
throw new Error(`workspace sync '${this.workId}' is already complete`);
|
|
24230
24264
|
} catch (error) {
|
|
24231
24265
|
this.#lastReceiptEffectId = previousReceipt;
|
|
24232
24266
|
throw error;
|
|
24233
24267
|
}
|
|
24234
24268
|
}
|
|
24235
|
-
#beginOffer(body, probe = false) {
|
|
24236
|
-
|
|
24269
|
+
#beginOffer(body, exchange, probe = false) {
|
|
24270
|
+
const phase = probe ? WorkspaceSyncPhase.Probe : WorkspaceSyncPhase.Offer;
|
|
24271
|
+
this.#state = { phase, exchange };
|
|
24237
24272
|
const token = this.#authToken();
|
|
24238
24273
|
this.#pushBytes += BigInt(body.byteLength);
|
|
24239
24274
|
return this.#remember({
|
|
@@ -24243,7 +24278,7 @@ var WorkspaceSyncWork = class {
|
|
|
24243
24278
|
effect: {
|
|
24244
24279
|
case: "http",
|
|
24245
24280
|
value: create(PeerHttpEffectSchema, {
|
|
24246
|
-
effectId: `${this.workId}:${
|
|
24281
|
+
effectId: `${this.workId}:${phase}`,
|
|
24247
24282
|
method: PeerHttpMethod.POST,
|
|
24248
24283
|
url: this.#offerEndpoint(),
|
|
24249
24284
|
contentType: CUSTODY_OFFER_MEDIA_TYPE,
|
|
@@ -24260,28 +24295,30 @@ var WorkspaceSyncWork = class {
|
|
|
24260
24295
|
})
|
|
24261
24296
|
});
|
|
24262
24297
|
}
|
|
24263
|
-
#acceptOffer(receipt) {
|
|
24264
|
-
|
|
24298
|
+
#acceptOffer(receipt, state) {
|
|
24299
|
+
const exchange = state.exchange;
|
|
24300
|
+
this.#pushMillis = exchange.startedAt === void 0 ? 0 : elapsedMillis(this.#clock, exchange.startedAt);
|
|
24265
24301
|
if (receipt.outcome.case !== "response") {
|
|
24266
24302
|
throw new Error("platform returned the wrong receipt for a custody offer");
|
|
24267
24303
|
}
|
|
24268
|
-
const
|
|
24269
|
-
if (
|
|
24304
|
+
const outcome = settleCustodyOfferExchange(exchange, receipt.outcome.value);
|
|
24305
|
+
if (outcome.case === "maintenance") {
|
|
24306
|
+
return this.#finishOfferOutcome(true, false);
|
|
24307
|
+
}
|
|
24308
|
+
if (outcome.case === "unexpected") {
|
|
24270
24309
|
this.#pushError = create(RuntimeTransportFailureSchema, {
|
|
24271
24310
|
endpoint: this.#offerEndpoint(),
|
|
24272
|
-
status:
|
|
24311
|
+
status: outcome.status,
|
|
24273
24312
|
refused: false,
|
|
24274
|
-
message: `Sync received an unexpected server response (HTTP ${
|
|
24313
|
+
message: `Sync received an unexpected server response (HTTP ${outcome.status}; ${outcome.contentType || "no content type"}). No custody receipt was returned.`
|
|
24275
24314
|
});
|
|
24276
24315
|
return this.#finishOfferOutcome(false);
|
|
24277
24316
|
}
|
|
24278
|
-
|
|
24279
|
-
const outcome = decodeCustodyOffer(response.body, this.#offerRequestId);
|
|
24280
|
-
this.#offerReceipt = custodyOfferReceipt(this.#offerRequestId, outcome);
|
|
24317
|
+
this.#offerReceipt = outcome.receipt;
|
|
24281
24318
|
if (outcome.case === "failure") {
|
|
24282
24319
|
this.#pushError = create(RuntimeTransportFailureSchema, {
|
|
24283
24320
|
endpoint: this.#offerEndpoint(),
|
|
24284
|
-
status:
|
|
24321
|
+
status: outcome.status,
|
|
24285
24322
|
refused: false,
|
|
24286
24323
|
message: `${outcome.value.code}: ${outcome.value.message}`
|
|
24287
24324
|
});
|
|
@@ -24290,12 +24327,12 @@ var WorkspaceSyncWork = class {
|
|
|
24290
24327
|
if (outcome.case === "blocked") {
|
|
24291
24328
|
return this.#finishOfferOutcome(true);
|
|
24292
24329
|
}
|
|
24293
|
-
if (
|
|
24330
|
+
if (outcome.status < 200 || outcome.status >= 300) {
|
|
24294
24331
|
this.#pushError = create(RuntimeTransportFailureSchema, {
|
|
24295
24332
|
endpoint: this.#offerEndpoint(),
|
|
24296
|
-
status:
|
|
24297
|
-
refused:
|
|
24298
|
-
message: `custody offer returned an admission verdict with HTTP ${
|
|
24333
|
+
status: outcome.status,
|
|
24334
|
+
refused: outcome.status === 422,
|
|
24335
|
+
message: `custody offer returned an admission verdict with HTTP ${outcome.status}`
|
|
24299
24336
|
});
|
|
24300
24337
|
return this.#finishOfferOutcome(false);
|
|
24301
24338
|
}
|
|
@@ -24303,7 +24340,7 @@ var WorkspaceSyncWork = class {
|
|
|
24303
24340
|
this.#desiredHead = admission.main || void 0;
|
|
24304
24341
|
if (!this.#desiredHead) return this.#transportFailure("resident peer returned no admitted frontier", false);
|
|
24305
24342
|
this.#candidateDurable = admission.sealedMain === this.#desiredHead;
|
|
24306
|
-
if (
|
|
24343
|
+
if (state.phase === WorkspaceSyncPhase.Probe && this.#desiredHead !== this.#localHead) {
|
|
24307
24344
|
if (!this.#establishBase(this.#desiredHead)) return this.#noCommonBaseResult();
|
|
24308
24345
|
if (this.#alreadyJudged(this.#desiredHead)) {
|
|
24309
24346
|
return this.#finish(create(SyncWorkspaceResponseSchema, {
|
|
@@ -24365,7 +24402,7 @@ var WorkspaceSyncWork = class {
|
|
|
24365
24402
|
if (admission.recoveryRequired !== false) return this.#leaseEffect();
|
|
24366
24403
|
return this.#transportFailure("resident peer supplied neither the missing delta nor a history gap", false);
|
|
24367
24404
|
}
|
|
24368
|
-
#acceptLease(receipt) {
|
|
24405
|
+
#acceptLease(receipt, state) {
|
|
24369
24406
|
if (receipt.outcome.case !== "response") {
|
|
24370
24407
|
throw new Error("platform returned the wrong receipt for a Git read lease");
|
|
24371
24408
|
}
|
|
@@ -24381,21 +24418,25 @@ var WorkspaceSyncWork = class {
|
|
|
24381
24418
|
this.#leased = true;
|
|
24382
24419
|
const frontier = this.#placementFrontier();
|
|
24383
24420
|
if (frontier) {
|
|
24384
|
-
|
|
24421
|
+
const transfer = new PlacementFetch(
|
|
24385
24422
|
this.#kernel,
|
|
24386
24423
|
this.#mount,
|
|
24387
24424
|
this.#requestId,
|
|
24388
24425
|
this.workId,
|
|
24389
24426
|
frontier
|
|
24390
24427
|
);
|
|
24391
|
-
const pending =
|
|
24428
|
+
const pending = transfer.start(lease);
|
|
24392
24429
|
if (pending) {
|
|
24393
|
-
this.#
|
|
24394
|
-
|
|
24430
|
+
this.#state = {
|
|
24431
|
+
phase: WorkspaceSyncPhase.Placement,
|
|
24432
|
+
transfer,
|
|
24433
|
+
lease,
|
|
24434
|
+
...state.afterPlacement ? { afterPlacement: state.afterPlacement } : {}
|
|
24435
|
+
};
|
|
24395
24436
|
return this.#remember({ case: "pending", value: pending });
|
|
24396
24437
|
}
|
|
24397
24438
|
}
|
|
24398
|
-
if (
|
|
24439
|
+
if (state.afterPlacement) return this.#finish(state.afterPlacement);
|
|
24399
24440
|
return this.#beginMainFetch(lease);
|
|
24400
24441
|
}
|
|
24401
24442
|
/** The provider's placement frontier as this round knows it: what the directory named in this
|
|
@@ -24415,8 +24456,7 @@ var WorkspaceSyncWork = class {
|
|
|
24415
24456
|
if (!new PlacementFetch(this.#kernel, this.#mount, this.#requestId, this.workId, frontier).wanted()) {
|
|
24416
24457
|
return void 0;
|
|
24417
24458
|
}
|
|
24418
|
-
this.#
|
|
24419
|
-
return this.#leaseEffect();
|
|
24459
|
+
return this.#leaseEffect(presented);
|
|
24420
24460
|
}
|
|
24421
24461
|
#acceptDiscovery(receipt) {
|
|
24422
24462
|
this.#directoryAnswered?.();
|
|
@@ -24440,9 +24480,11 @@ var WorkspaceSyncWork = class {
|
|
|
24440
24480
|
return this.#replaceLocalHead !== void 0 || this.#localHead === void 0 && this.#previousHead === void 0;
|
|
24441
24481
|
}
|
|
24442
24482
|
#beginMainFetch(lease) {
|
|
24443
|
-
this.#phase = WorkspaceSyncPhase.Fetch;
|
|
24444
24483
|
const lawProducts = this.#lawProductsAlongside();
|
|
24445
|
-
this.#
|
|
24484
|
+
this.#state = {
|
|
24485
|
+
phase: WorkspaceSyncPhase.Fetch,
|
|
24486
|
+
startedAt: this.#clock.monotonicNanoseconds()
|
|
24487
|
+
};
|
|
24446
24488
|
const haves = this.#replaceLocalHead !== void 0 ? this.#repairMissingCommit ? [] : [this.#kernel.resolveRef(this.#mount, this.#requestId, deferredRemote)].filter(
|
|
24447
24489
|
(head) => !!head
|
|
24448
24490
|
) : [...new Set([
|
|
@@ -24473,7 +24515,7 @@ var WorkspaceSyncWork = class {
|
|
|
24473
24515
|
})
|
|
24474
24516
|
});
|
|
24475
24517
|
}
|
|
24476
|
-
#acceptFetch(receipt) {
|
|
24518
|
+
#acceptFetch(receipt, state) {
|
|
24477
24519
|
if (receipt.outcome.case !== "gitFetch") {
|
|
24478
24520
|
throw new Error("platform returned the wrong receipt for a Git object fetch");
|
|
24479
24521
|
}
|
|
@@ -24483,7 +24525,7 @@ var WorkspaceSyncWork = class {
|
|
|
24483
24525
|
if (fetched.pack.byteLength > 0) {
|
|
24484
24526
|
this.#kernel.applyPack(this.#mount, this.#requestId, fetched.pack, fetched.head, fetched.shallow);
|
|
24485
24527
|
}
|
|
24486
|
-
const prefetchMillis =
|
|
24528
|
+
const prefetchMillis = elapsedMillis(this.#clock, state.startedAt);
|
|
24487
24529
|
const candidateHead = this.#receiveState ? this.#kernel.resolveStatePosition?.(this.#mount, this.#requestId, fetched.head, this.#receiveState) : fetched.head;
|
|
24488
24530
|
if (!candidateHead) {
|
|
24489
24531
|
return this.#finish(create(SyncWorkspaceResponseSchema, {
|
|
@@ -24834,11 +24876,11 @@ var WorkspaceSyncWork = class {
|
|
|
24834
24876
|
pushBytes: this.#pushBytes,
|
|
24835
24877
|
pullBytes: BigInt(receivedBytes),
|
|
24836
24878
|
serverMillis: this.#admissionSummary?.serverMillis ?? 0,
|
|
24837
|
-
offerInPush: this.#
|
|
24879
|
+
offerInPush: this.#pushBytes > 0n
|
|
24838
24880
|
})
|
|
24839
24881
|
});
|
|
24840
24882
|
}
|
|
24841
|
-
#finishOfferOutcome(blocked) {
|
|
24883
|
+
#finishOfferOutcome(blocked, reconnectRequired = true) {
|
|
24842
24884
|
return this.#finish(create(SyncWorkspaceResponseSchema, {
|
|
24843
24885
|
workspace: this.#mount.workspace,
|
|
24844
24886
|
previousHead: this.#previousHead,
|
|
@@ -24846,7 +24888,7 @@ var WorkspaceSyncWork = class {
|
|
|
24846
24888
|
admitted: BigInt(this.#admissionSummary?.admittedIntentIds.length ?? 0),
|
|
24847
24889
|
refused: BigInt((this.#admissionSummary?.refusedMessages.length ?? 0) + this.#parkedLocal),
|
|
24848
24890
|
deferred: BigInt(this.#admissionSummary ? this.#admissionSummary.blockedMessages.length + this.#admissionSummary.deadLetteredMessages.length + this.#admissionSummary.quarantinedMessages.length + this.#admissionSummary.deferredMessages.length : blocked ? 1 : 0),
|
|
24849
|
-
reconnectRequired
|
|
24891
|
+
reconnectRequired,
|
|
24850
24892
|
stillAhead: this.#localHead !== this.#previousHead,
|
|
24851
24893
|
directOfferDeferred: blocked,
|
|
24852
24894
|
offerReceipt: this.#offerReceipt,
|
|
@@ -24860,15 +24902,16 @@ var WorkspaceSyncWork = class {
|
|
|
24860
24902
|
}));
|
|
24861
24903
|
}
|
|
24862
24904
|
#transportFailure(message, offline, status = -1) {
|
|
24863
|
-
const
|
|
24905
|
+
const state = this.#state;
|
|
24906
|
+
const pushFailed = state.phase === WorkspaceSyncPhase.Offer;
|
|
24864
24907
|
const failedEndpoint = this.#offerEndpoint();
|
|
24865
24908
|
const failure = create(RuntimeTransportFailureSchema, {
|
|
24866
24909
|
endpoint: pushFailed ? failedEndpoint : this.#binding.cloud ?? "",
|
|
24867
24910
|
status,
|
|
24868
24911
|
message
|
|
24869
24912
|
});
|
|
24870
|
-
if (
|
|
24871
|
-
this.#pushMillis =
|
|
24913
|
+
if (state.phase === WorkspaceSyncPhase.Offer) {
|
|
24914
|
+
this.#pushMillis = state.exchange.startedAt === void 0 ? 0 : elapsedMillis(this.#clock, state.exchange.startedAt);
|
|
24872
24915
|
this.#pushError = failure;
|
|
24873
24916
|
}
|
|
24874
24917
|
return this.#finish(create(SyncWorkspaceResponseSchema, {
|
|
@@ -24885,7 +24928,7 @@ var WorkspaceSyncWork = class {
|
|
|
24885
24928
|
pushMillis: this.#pushMillis,
|
|
24886
24929
|
pushBytes: this.#pushBytes,
|
|
24887
24930
|
prefetchMillis: elapsedMillis(this.#clock, this.#started),
|
|
24888
|
-
offerInPush: this.#
|
|
24931
|
+
offerInPush: this.#pushBytes > 0n
|
|
24889
24932
|
})
|
|
24890
24933
|
}));
|
|
24891
24934
|
}
|
|
@@ -24895,7 +24938,7 @@ var WorkspaceSyncWork = class {
|
|
|
24895
24938
|
return step;
|
|
24896
24939
|
}
|
|
24897
24940
|
#finish(value) {
|
|
24898
|
-
this.#
|
|
24941
|
+
this.#state = { phase: WorkspaceSyncPhase.Done };
|
|
24899
24942
|
this.#current = void 0;
|
|
24900
24943
|
return this.#remember({ case: "synced", value });
|
|
24901
24944
|
}
|
|
@@ -543,6 +543,8 @@ final class NmsSyncStatusProjection {
|
|
|
543
543
|
for (final stream in streams) {
|
|
544
544
|
if (!stream.isClosed) unawaited(stream.close());
|
|
545
545
|
}
|
|
546
|
-
|
|
546
|
+
// A paused UI subscriber may not process the done event until it resumes.
|
|
547
|
+
// Closing this broadcast stream must not hold reception teardown open.
|
|
548
|
+
if (!_application.isClosed) unawaited(_application.close());
|
|
547
549
|
}
|
|
548
550
|
}
|
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.
|
|
10
|
+
version: 0.66.6
|
|
11
11
|
|
|
12
12
|
environment:
|
|
13
13
|
sdk: ^3.5.0
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import 'package:nomos_client/src/reception/event_desk.dart';
|
|
2
|
+
import 'package:test/test.dart';
|
|
3
|
+
|
|
4
|
+
void main() {
|
|
5
|
+
test('reception closes while presentation observers are paused', () async {
|
|
6
|
+
final desk = ReceptionEventDesk();
|
|
7
|
+
final copies = desk.copyChanges.listen((_) {})..pause();
|
|
8
|
+
final application = desk.syncStatuses.applicationStream.listen((_) {})
|
|
9
|
+
..pause();
|
|
10
|
+
|
|
11
|
+
await desk.close().timeout(const Duration(seconds: 1));
|
|
12
|
+
|
|
13
|
+
await copies.cancel();
|
|
14
|
+
await application.cancel();
|
|
15
|
+
});
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.110.
|
|
3
|
+
"version": "0.110.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.110.
|
|
75
|
+
"@githolon/runtime": "^0.110.1",
|
|
76
76
|
"@bufbuild/protobuf": "^2.13.0",
|
|
77
77
|
"@bufbuild/protoc-gen-es": "^2.13.0",
|
|
78
78
|
"esbuild": "0.24.2",
|
|
@@ -1237,7 +1237,7 @@ export function emitDartCallProtoEncoder(
|
|
|
1237
1237
|
for (const field of fields) {
|
|
1238
1238
|
const fieldAccess = access.get(field.name);
|
|
1239
1239
|
if (fieldAccess?.omit === true || fieldAccess?.present === "false") continue;
|
|
1240
|
-
const raw = fieldAccess?.slot === true ? `${fieldAccess.value}.
|
|
1240
|
+
const raw = fieldAccess?.slot === true ? `${fieldAccess.value}.valueOrNull` : fieldAccess?.value ?? `payload.${dartMemberName(field.name)}`;
|
|
1241
1241
|
const fieldSemantic = typeNode(spec.nodes, field.value.id).expression.semantic;
|
|
1242
1242
|
const fieldValueIsCanonical = fieldSemantic?.kind === "id-reference" ||
|
|
1243
1243
|
fieldSemantic?.kind === "address-reference" ||
|
|
@@ -1252,7 +1252,9 @@ export function emitDartCallProtoEncoder(
|
|
|
1252
1252
|
emitValue(body, raw, field.value.id, field.name, (expression) => {
|
|
1253
1253
|
body.push(`${member} = ${expression};`);
|
|
1254
1254
|
}, fieldValueIsCanonical);
|
|
1255
|
-
if (
|
|
1255
|
+
if (fieldAccess?.slot === true) {
|
|
1256
|
+
lines.push(` if (!${fieldAccess.value}.isOmitted) {`, ...body.map((line) => ` ${line}`), ` }`);
|
|
1257
|
+
} else if (field.required) lines.push(...body.map((line) => ` ${line}`));
|
|
1256
1258
|
else lines.push(` if (${fieldAccess?.present ?? `${raw} != null`}) {`, ...body.map((line) => ` ${line}`), ` }`);
|
|
1257
1259
|
}
|
|
1258
1260
|
lines.push(
|
package/src/codegen_dart.ts
CHANGED
|
@@ -5479,6 +5479,10 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
5479
5479
|
* Every tuple and subject is nominally typed from the same DSL graph; there is no JSON leaf or map-shaped
|
|
5480
5480
|
* boundary. INFORMATIONAL only (the one wasm gate is still authoritative). */
|
|
5481
5481
|
export function dartRoleHelpers(mod: DomainModule): string {
|
|
5482
|
+
// Some packages have role policy declarations but do not project the framework
|
|
5483
|
+
// RelationTuple aggregate into this domain. In that case there is no typed
|
|
5484
|
+
// read model for a client-side hint to consume.
|
|
5485
|
+
if (!mod.aggregates.some((aggregate) => aggregate.id === "RelationTuple")) return "";
|
|
5482
5486
|
const editRels = new Map<string, Set<string>>();
|
|
5483
5487
|
const viewRels = new Map<string, Set<string>>();
|
|
5484
5488
|
for (const r of mod.roles ?? []) {
|
|
@@ -2544,9 +2544,10 @@ const NOMOS_TYPES_DEP = "^0.11.2";
|
|
|
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.
|
|
2548
|
-
|
|
2549
|
-
//
|
|
2547
|
+
const NOMOS_CLIENT_DEP = "^0.66.6";
|
|
2548
|
+
const NOMOS_FLUTTER_DEP = "^0.65.8";
|
|
2549
|
+
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in the Dart and Flutter
|
|
2550
|
+
// package pubspecs — a release without this bump fails the suite, so tenants never
|
|
2550
2551
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
2551
2552
|
|
|
2552
2553
|
const isPubPackageName = (s: string): boolean => /^[a-z][a-z0-9_]*$/.test(s);
|
|
@@ -3166,10 +3167,13 @@ function reconcilePubspec(
|
|
|
3166
3167
|
index < dependenciesEnd &&
|
|
3167
3168
|
line.startsWith(prefix)
|
|
3168
3169
|
);
|
|
3169
|
-
const replacement =
|
|
3170
|
+
const replacement =
|
|
3171
|
+
name === "flutter" && constraint === "sdk:flutter"
|
|
3172
|
+
? [" flutter:", " sdk: flutter"]
|
|
3173
|
+
: [` ${name}: ${constraint}`];
|
|
3170
3174
|
if (existing < 0) {
|
|
3171
|
-
lines.splice(dependenciesEnd, 0, replacement);
|
|
3172
|
-
dependenciesEnd +=
|
|
3175
|
+
lines.splice(dependenciesEnd, 0, ...replacement);
|
|
3176
|
+
dependenciesEnd += replacement.length;
|
|
3173
3177
|
continue;
|
|
3174
3178
|
}
|
|
3175
3179
|
let nestedEnd = existing + 1;
|
|
@@ -3178,8 +3182,8 @@ function reconcilePubspec(
|
|
|
3178
3182
|
/^(?: {4,}|\t)/.test(lines[nestedEnd]!)
|
|
3179
3183
|
)
|
|
3180
3184
|
nestedEnd += 1;
|
|
3181
|
-
lines.splice(existing, nestedEnd - existing, replacement);
|
|
3182
|
-
dependenciesEnd
|
|
3185
|
+
lines.splice(existing, nestedEnd - existing, ...replacement);
|
|
3186
|
+
dependenciesEnd += replacement.length - (nestedEnd - existing);
|
|
3183
3187
|
}
|
|
3184
3188
|
|
|
3185
3189
|
const next = `${lines.join("\n")}${trailingNewline ? "\n" : ""}`;
|
|
@@ -4045,6 +4049,7 @@ function emitDartPackages(
|
|
|
4045
4049
|
` flutter:`,
|
|
4046
4050
|
` sdk: flutter`,
|
|
4047
4051
|
` nomos_client: ${NOMOS_CLIENT_DEP}`,
|
|
4052
|
+
` nomos_flutter: ${NOMOS_FLUTTER_DEP}`,
|
|
4048
4053
|
` ${packages.client}:`,
|
|
4049
4054
|
` path: ../${packages.client} # co-emitted sibling; replace with a hosted constraint when publishing`,
|
|
4050
4055
|
`dev_dependencies:`,
|
|
@@ -4054,7 +4059,11 @@ function emitDartPackages(
|
|
|
4054
4059
|
` - lib/${genRel}/${embeddedLawName}`,
|
|
4055
4060
|
``,
|
|
4056
4061
|
].join("\n"),
|
|
4057
|
-
{
|
|
4062
|
+
{
|
|
4063
|
+
flutter: "sdk:flutter",
|
|
4064
|
+
nomos_client: NOMOS_CLIENT_DEP,
|
|
4065
|
+
nomos_flutter: NOMOS_FLUTTER_DEP,
|
|
4066
|
+
}
|
|
4058
4067
|
);
|
|
4059
4068
|
reconcileGeneratedLawAsset(
|
|
4060
4069
|
pkgDir(packages.test),
|
|
@@ -8918,12 +8927,14 @@ async function main(): Promise<void> {
|
|
|
8918
8927
|
const client = path.resolve(selfDir, "..", "dart");
|
|
8919
8928
|
const types = path.resolve(selfDir, "..", "dart_types");
|
|
8920
8929
|
const peerHost = path.resolve(selfDir, "..", "..", "nomos_peer_host");
|
|
8930
|
+
const flutter = path.resolve(selfDir, "..", "..", "nomos_flutter");
|
|
8921
8931
|
if (
|
|
8922
8932
|
existsSync(path.join(client, "pubspec.yaml")) &&
|
|
8923
8933
|
existsSync(path.join(types, "pubspec.yaml")) &&
|
|
8924
|
-
existsSync(path.join(peerHost, "pubspec.yaml"))
|
|
8934
|
+
existsSync(path.join(peerHost, "pubspec.yaml")) &&
|
|
8935
|
+
existsSync(path.join(flutter, "pubspec.yaml"))
|
|
8925
8936
|
) {
|
|
8926
|
-
return { client, types, peerHost };
|
|
8937
|
+
return { client, types, peerHost, flutter };
|
|
8927
8938
|
}
|
|
8928
8939
|
return undefined;
|
|
8929
8940
|
})();
|
|
@@ -8961,6 +8972,7 @@ async function main(): Promise<void> {
|
|
|
8961
8972
|
const lines = ["dependency_overrides:"];
|
|
8962
8973
|
const usesClient = /(^|\n)\s+nomos_client\s*:/.test(spec);
|
|
8963
8974
|
const usesTypes = /(^|\n)\s+nomos_types\s*:/.test(spec);
|
|
8975
|
+
const usesFlutter = /(^|\n)\s+nomos_flutter\s*:/.test(spec);
|
|
8964
8976
|
if (usesClient)
|
|
8965
8977
|
lines.push(` nomos_client:`, ` path: ${localDart.client}`);
|
|
8966
8978
|
// A generated CLIENT or TEST package may only mention nomos_client directly, but that local
|
|
@@ -8978,6 +8990,8 @@ async function main(): Promise<void> {
|
|
|
8978
8990
|
` nomos_peer_host:`,
|
|
8979
8991
|
` path: ${localDart.peerHost}`
|
|
8980
8992
|
);
|
|
8993
|
+
if (usesFlutter)
|
|
8994
|
+
lines.push(` nomos_flutter:`, ` path: ${localDart.flutter}`);
|
|
8981
8995
|
if (lines.length > 1) {
|
|
8982
8996
|
writeTextIfChanged(overrideFile, lines.join("\n") + "\n");
|
|
8983
8997
|
}
|
|
@@ -9039,7 +9053,10 @@ async function main(): Promise<void> {
|
|
|
9039
9053
|
for (const pkg of pkgDirs) {
|
|
9040
9054
|
if (dartPackageResolutionCurrent(pkg)) continue;
|
|
9041
9055
|
try {
|
|
9042
|
-
|
|
9056
|
+
const isFlutterPackage = /^\s{2}flutter:\s*\n\s{4}sdk:\s*flutter\s*$/m.test(
|
|
9057
|
+
readFileSync(path.join(pkg, "pubspec.yaml"), "utf8")
|
|
9058
|
+
);
|
|
9059
|
+
execFileSync(isFlutterPackage ? "flutter" : "dart", ["pub", "get"], {
|
|
9043
9060
|
cwd: pkg,
|
|
9044
9061
|
stdio: "pipe",
|
|
9045
9062
|
encoding: "utf8",
|