@spine-event-engine/server 2.0.0-snapshot.23 → 2.0.0-snapshot.25
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/README.md +38 -11
- package/REFERENCE.md +278 -141
- package/dist/agent/agent-acceptance-receipts.d.ts +69 -0
- package/dist/agent/agent-acceptance-receipts.d.ts.map +1 -0
- package/dist/agent/agent-acceptance-receipts.js +198 -0
- package/dist/agent/agent-acceptance-receipts.js.map +1 -0
- package/dist/agent/agent-execution-capacity.d.ts +37 -9
- package/dist/agent/agent-execution-capacity.d.ts.map +1 -1
- package/dist/agent/agent-execution-capacity.js +73 -15
- package/dist/agent/agent-execution-capacity.js.map +1 -1
- package/dist/agent/agent-history.d.ts +15 -10
- package/dist/agent/agent-history.d.ts.map +1 -1
- package/dist/agent/agent-history.js +26 -14
- package/dist/agent/agent-history.js.map +1 -1
- package/dist/agent/agent-live-ai-audit.d.ts +95 -0
- package/dist/agent/agent-live-ai-audit.d.ts.map +1 -0
- package/dist/agent/agent-live-ai-audit.js +290 -0
- package/dist/agent/agent-live-ai-audit.js.map +1 -0
- package/dist/agent/agent-live-ai-runtime.d.ts +300 -0
- package/dist/agent/agent-live-ai-runtime.d.ts.map +1 -0
- package/dist/agent/agent-live-ai-runtime.js +611 -0
- package/dist/agent/agent-live-ai-runtime.js.map +1 -0
- package/dist/agent/agent-live-audit.d.ts +67 -0
- package/dist/agent/agent-live-audit.d.ts.map +1 -0
- package/dist/agent/agent-live-audit.js +121 -0
- package/dist/agent/agent-live-audit.js.map +1 -0
- package/dist/agent/agent-live-budget.d.ts +84 -0
- package/dist/agent/agent-live-budget.d.ts.map +1 -0
- package/dist/agent/agent-live-budget.js +161 -0
- package/dist/agent/agent-live-budget.js.map +1 -0
- package/dist/agent/agent-live-fault.d.ts +18 -0
- package/dist/agent/agent-live-fault.d.ts.map +1 -0
- package/dist/agent/{agent-execution-fault.js → agent-live-fault.js} +7 -7
- package/dist/agent/agent-live-fault.js.map +1 -0
- package/dist/agent/agent-live-inputs.d.ts +74 -0
- package/dist/agent/agent-live-inputs.d.ts.map +1 -0
- package/dist/agent/agent-live-inputs.js +15 -0
- package/dist/agent/agent-live-inputs.js.map +1 -0
- package/dist/agent/agent-live-interaction-events.d.ts +46 -0
- package/dist/agent/agent-live-interaction-events.d.ts.map +1 -0
- package/dist/agent/agent-live-interaction-events.js +104 -0
- package/dist/agent/agent-live-interaction-events.js.map +1 -0
- package/dist/agent/agent-live-mcp-controls.d.ts +129 -0
- package/dist/agent/agent-live-mcp-controls.d.ts.map +1 -0
- package/dist/agent/agent-live-mcp-controls.js +228 -0
- package/dist/agent/agent-live-mcp-controls.js.map +1 -0
- package/dist/agent/agent-live-mcp-host.d.ts +88 -0
- package/dist/agent/agent-live-mcp-host.d.ts.map +1 -0
- package/dist/agent/{agent-mcp-host.js → agent-live-mcp-host.js} +1 -1
- package/dist/agent/agent-live-mcp-host.js.map +1 -0
- package/dist/agent/{agent-mcp-runtime.d.ts → agent-live-mcp-runtime.d.ts} +6 -6
- package/dist/agent/agent-live-mcp-runtime.d.ts.map +1 -0
- package/dist/agent/{agent-mcp-runtime.js → agent-live-mcp-runtime.js} +13 -42
- package/dist/agent/agent-live-mcp-runtime.js.map +1 -0
- package/dist/agent/agent-live-read-runtime.d.ts +61 -0
- package/dist/agent/agent-live-read-runtime.d.ts.map +1 -0
- package/dist/agent/agent-live-read-runtime.js +121 -0
- package/dist/agent/agent-live-read-runtime.js.map +1 -0
- package/dist/agent/agent-model-selection.d.ts +21 -34
- package/dist/agent/agent-model-selection.d.ts.map +1 -1
- package/dist/agent/agent-model-selection.js +61 -78
- package/dist/agent/agent-model-selection.js.map +1 -1
- package/dist/bus/command-bus.d.ts +14 -18
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +31 -48
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/event-bus.d.ts +82 -104
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +87 -127
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/context/bounded-context.d.ts +143 -91
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +753 -359
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/entity-inbox.d.ts +7 -2
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +43 -20
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +35 -34
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +123 -135
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/projection-handoff.d.ts +2 -2
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +12 -8
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts +36 -31
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +123 -125
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/context/tenant-unknown-error.d.ts +33 -0
- package/dist/context/tenant-unknown-error.d.ts.map +1 -0
- package/dist/context/tenant-unknown-error.js +75 -0
- package/dist/context/tenant-unknown-error.js.map +1 -0
- package/dist/delivery/agent-delivery-scope.d.ts +66 -0
- package/dist/delivery/agent-delivery-scope.d.ts.map +1 -0
- package/dist/delivery/agent-delivery-scope.js +44 -0
- package/dist/delivery/agent-delivery-scope.js.map +1 -0
- package/dist/delivery/delivery-builder.d.ts +25 -1
- package/dist/delivery/delivery-builder.d.ts.map +1 -1
- package/dist/delivery/delivery-builder.js +89 -0
- package/dist/delivery/delivery-builder.js.map +1 -1
- package/dist/delivery/delivery-monitor.d.ts +12 -9
- package/dist/delivery/delivery-monitor.d.ts.map +1 -1
- package/dist/delivery/delivery-monitor.js +15 -9
- package/dist/delivery/delivery-monitor.js.map +1 -1
- package/dist/delivery/delivery-ports.d.ts +6 -13
- package/dist/delivery/delivery-ports.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.d.ts +21 -0
- package/dist/delivery/delivery-run-control.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.js +10 -0
- package/dist/delivery/delivery-run-control.js.map +1 -1
- package/dist/delivery/delivery-supervisor.d.ts +33 -5
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
- package/dist/delivery/delivery-supervisor.js +178 -26
- package/dist/delivery/delivery-supervisor.js.map +1 -1
- package/dist/delivery/delivery-tenant.d.ts +17 -0
- package/dist/delivery/delivery-tenant.d.ts.map +1 -0
- package/dist/delivery/delivery-tenant.js +50 -0
- package/dist/delivery/delivery-tenant.js.map +1 -0
- package/dist/delivery/delivery.d.ts +96 -2
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +803 -89
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-records.d.ts.map +1 -1
- package/dist/delivery/inbox-records.js +9 -16
- package/dist/delivery/inbox-records.js.map +1 -1
- package/dist/delivery/inbox-storage.d.ts +14 -11
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +72 -78
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts +9 -12
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +13 -15
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +64 -0
- package/dist/delivery/reception-observation.d.ts.map +1 -0
- package/dist/delivery/reception-observation.js +78 -0
- package/dist/delivery/reception-observation.js.map +1 -0
- package/dist/delivery/sharded-work-registry.d.ts +6 -0
- package/dist/delivery/sharded-work-registry.d.ts.map +1 -1
- package/dist/delivery/sharded-work-registry.js +41 -4
- package/dist/delivery/sharded-work-registry.js.map +1 -1
- package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
- package/dist/entity/entity-storage-descriptor.js +7 -4
- package/dist/entity/entity-storage-descriptor.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/integration/external-messages.d.ts.map +1 -1
- package/dist/integration/external-messages.js +9 -15
- package/dist/integration/external-messages.js.map +1 -1
- package/dist/integration/integration-broker.d.ts.map +1 -1
- package/dist/integration/integration-broker.js +112 -6
- package/dist/integration/integration-broker.js.map +1 -1
- package/dist/integration/third-party-context.d.ts.map +1 -1
- package/dist/integration/third-party-context.js +7 -10
- package/dist/integration/third-party-context.js.map +1 -1
- package/dist/repository/primitive-id.js +26 -11
- package/dist/repository/primitive-id.js.map +1 -1
- package/dist/repository/repository.d.ts +33 -62
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +1475 -1827
- package/dist/repository/repository.js.map +1 -1
- package/dist/runtime/runtime.d.ts +24 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +31 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/signal-metadata.d.ts.map +1 -1
- package/dist/runtime/signal-metadata.js +8 -15
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +8 -36
- package/dist/runtime/signal-publisher.d.ts.map +1 -1
- package/dist/runtime/signal-publisher.js +68 -61
- package/dist/runtime/signal-publisher.js.map +1 -1
- package/dist/runtime/standalone-handler-runtime.d.ts +0 -7
- package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -1
- package/dist/runtime/standalone-handler-runtime.js +14 -130
- package/dist/runtime/standalone-handler-runtime.js.map +1 -1
- package/dist/server/managed-delivery-gate.d.ts +34 -0
- package/dist/server/managed-delivery-gate.d.ts.map +1 -0
- package/dist/server/managed-delivery-gate.js +70 -0
- package/dist/server/managed-delivery-gate.js.map +1 -0
- package/dist/server/managed-server-application.d.ts +2 -2
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +471 -178
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/node-coordinator.d.ts +14 -0
- package/dist/server/node-coordinator.d.ts.map +1 -1
- package/dist/server/node-coordinator.js +266 -45
- package/dist/server/node-coordinator.js.map +1 -1
- package/dist/server/server-environment.d.ts +203 -25
- package/dist/server/server-environment.d.ts.map +1 -1
- package/dist/server/server-environment.js +661 -133
- package/dist/server/server-environment.js.map +1 -1
- package/dist/server/server-log.d.ts +7 -0
- package/dist/server/server-log.d.ts.map +1 -1
- package/dist/server/server-log.js +18 -1
- package/dist/server/server-log.js.map +1 -1
- package/dist/server/server.d.ts +18 -32
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +62 -203
- package/dist/server/server.js.map +1 -1
- package/dist/services/spine-services.d.ts +1 -1
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +399 -370
- package/dist/services/spine-services.js.map +1 -1
- package/dist/stand/event-origin-filter.d.ts +16 -0
- package/dist/stand/event-origin-filter.d.ts.map +1 -0
- package/dist/stand/event-origin-filter.js +124 -0
- package/dist/stand/event-origin-filter.js.map +1 -0
- package/dist/stand/stand.d.ts +14 -0
- package/dist/stand/stand.d.ts.map +1 -1
- package/dist/stand/stand.js +53 -9
- package/dist/stand/stand.js.map +1 -1
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +61 -40
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-registry.d.ts +6 -2
- package/dist/stand/subscription-registry.d.ts.map +1 -1
- package/dist/stand/subscription-registry.js +15 -11
- package/dist/stand/subscription-registry.js.map +1 -1
- package/dist/stand/subscription-runtime.d.ts +23 -4
- package/dist/stand/subscription-runtime.d.ts.map +1 -1
- package/dist/stand/subscription-runtime.js +84 -13
- package/dist/stand/subscription-runtime.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -15
- package/dist/agent/agent-admission.d.ts +0 -80
- package/dist/agent/agent-admission.d.ts.map +0 -1
- package/dist/agent/agent-admission.js +0 -127
- package/dist/agent/agent-admission.js.map +0 -1
- package/dist/agent/agent-ai-runtime.d.ts +0 -76
- package/dist/agent/agent-ai-runtime.d.ts.map +0 -1
- package/dist/agent/agent-ai-runtime.js +0 -1269
- package/dist/agent/agent-ai-runtime.js.map +0 -1
- package/dist/agent/agent-execution-fault.d.ts +0 -18
- package/dist/agent/agent-execution-fault.d.ts.map +0 -1
- package/dist/agent/agent-execution-fault.js.map +0 -1
- package/dist/agent/agent-execution-session.d.ts +0 -103
- package/dist/agent/agent-execution-session.d.ts.map +0 -1
- package/dist/agent/agent-execution-session.js +0 -238
- package/dist/agent/agent-execution-session.js.map +0 -1
- package/dist/agent/agent-interaction-audit.d.ts +0 -58
- package/dist/agent/agent-interaction-audit.d.ts.map +0 -1
- package/dist/agent/agent-interaction-audit.js +0 -114
- package/dist/agent/agent-interaction-audit.js.map +0 -1
- package/dist/agent/agent-interaction-events.d.ts +0 -40
- package/dist/agent/agent-interaction-events.d.ts.map +0 -1
- package/dist/agent/agent-interaction-events.js +0 -129
- package/dist/agent/agent-interaction-events.js.map +0 -1
- package/dist/agent/agent-mcp-budget.d.ts +0 -125
- package/dist/agent/agent-mcp-budget.d.ts.map +0 -1
- package/dist/agent/agent-mcp-budget.js +0 -566
- package/dist/agent/agent-mcp-budget.js.map +0 -1
- package/dist/agent/agent-mcp-host.d.ts +0 -125
- package/dist/agent/agent-mcp-host.d.ts.map +0 -1
- package/dist/agent/agent-mcp-host.js.map +0 -1
- package/dist/agent/agent-mcp-runtime.d.ts.map +0 -1
- package/dist/agent/agent-mcp-runtime.js.map +0 -1
- package/dist/agent/agent-read-runtime.d.ts +0 -70
- package/dist/agent/agent-read-runtime.d.ts.map +0 -1
- package/dist/agent/agent-read-runtime.js +0 -320
- package/dist/agent/agent-read-runtime.js.map +0 -1
- package/dist/agent/agent-revisions.d.ts +0 -35
- package/dist/agent/agent-revisions.d.ts.map +0 -1
- package/dist/agent/agent-revisions.js +0 -133
- package/dist/agent/agent-revisions.js.map +0 -1
- package/dist/agent/agent-scheduler.d.ts +0 -99
- package/dist/agent/agent-scheduler.d.ts.map +0 -1
- package/dist/agent/agent-scheduler.js +0 -398
- package/dist/agent/agent-scheduler.js.map +0 -1
- package/dist/bus/saved-dispatcher-binding.d.ts +0 -96
- package/dist/bus/saved-dispatcher-binding.d.ts.map +0 -1
- package/dist/bus/saved-dispatcher-binding.js +0 -35
- package/dist/bus/saved-dispatcher-binding.js.map +0 -1
- package/dist/delivery/delivery-run-coordinator.d.ts +0 -218
- package/dist/delivery/delivery-run-coordinator.d.ts.map +0 -1
- package/dist/delivery/delivery-run-coordinator.js +0 -609
- package/dist/delivery/delivery-run-coordinator.js.map +0 -1
- package/dist/delivery/parked-delivery-obligations.d.ts +0 -182
- package/dist/delivery/parked-delivery-obligations.d.ts.map +0 -1
- package/dist/delivery/parked-delivery-obligations.js +0 -390
- package/dist/delivery/parked-delivery-obligations.js.map +0 -1
- package/dist/server/environment-attachment.d.ts +0 -356
- package/dist/server/environment-attachment.d.ts.map +0 -1
- package/dist/server/environment-attachment.js +0 -2062
- package/dist/server/environment-attachment.js.map +0 -1
- package/dist/server/environment-delivery-records.d.ts +0 -77
- package/dist/server/environment-delivery-records.d.ts.map +0 -1
- package/dist/server/environment-delivery-records.js +0 -282
- package/dist/server/environment-delivery-records.js.map +0 -1
- package/dist/server/environment-delivery-worker.d.ts +0 -161
- package/dist/server/environment-delivery-worker.d.ts.map +0 -1
- package/dist/server/environment-delivery-worker.js +0 -673
- package/dist/server/environment-delivery-worker.js.map +0 -1
|
@@ -1,2062 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2026, CodeMatters. All rights reserved.
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
5
|
-
* in compliance with the License. You may obtain a copy of the License at
|
|
6
|
-
*
|
|
7
|
-
* https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
*
|
|
9
|
-
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
|
10
|
-
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
11
|
-
* or implied. See the License for the specific language governing permissions and limitations under
|
|
12
|
-
* the License.
|
|
13
|
-
*/
|
|
14
|
-
import { clone } from "@bufbuild/protobuf";
|
|
15
|
-
import { TenantIdSchema } from "@spine-event-engine/proto";
|
|
16
|
-
import {} from "@spine-event-engine/storage";
|
|
17
|
-
import { TenantBoundary } from "@spine-event-engine/storage/provider";
|
|
18
|
-
import { DeliveryRunCoordinator, } from "../delivery/delivery-run-coordinator.js";
|
|
19
|
-
import { ParkedDeliveryObligations, } from "../delivery/parked-delivery-obligations.js";
|
|
20
|
-
import { ShardIndex } from "../delivery/shard-index.js";
|
|
21
|
-
import { EnvironmentDeliveryWorker, } from "./environment-delivery-worker.js";
|
|
22
|
-
import { EnvironmentDeliveryRecords } from "./environment-delivery-records.js";
|
|
23
|
-
class AttachedEnvironmentRegistration {
|
|
24
|
-
startup;
|
|
25
|
-
token;
|
|
26
|
-
#binding;
|
|
27
|
-
#records;
|
|
28
|
-
constructor(claim, binding, startup, records) {
|
|
29
|
-
this.token = claim.token;
|
|
30
|
-
this.#binding = binding;
|
|
31
|
-
this.startup = startup;
|
|
32
|
-
this.#records = records;
|
|
33
|
-
Object.freeze(this);
|
|
34
|
-
}
|
|
35
|
-
get generation() {
|
|
36
|
-
return this.#binding.generation;
|
|
37
|
-
}
|
|
38
|
-
records() {
|
|
39
|
-
return this.#records();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Tracks registration ownership and generations for one server environment.
|
|
44
|
-
*
|
|
45
|
-
* @internal
|
|
46
|
-
*/
|
|
47
|
-
export class EnvironmentRegistrations {
|
|
48
|
-
#claims = new Map();
|
|
49
|
-
#generation;
|
|
50
|
-
#ownership;
|
|
51
|
-
#nextToken = 0;
|
|
52
|
-
/**
|
|
53
|
-
* Returns the number of active claims.
|
|
54
|
-
*
|
|
55
|
-
* @returns The active claim count.
|
|
56
|
-
*/
|
|
57
|
-
get count() {
|
|
58
|
-
return this.#claims.size;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Returns the active generation, if any.
|
|
62
|
-
*
|
|
63
|
-
* @returns The current generation or `undefined`.
|
|
64
|
-
*/
|
|
65
|
-
get generation() {
|
|
66
|
-
return this.#generation;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Sets the current generation during a safe handoff.
|
|
70
|
-
*
|
|
71
|
-
* @param generation Supplies the replacement generation.
|
|
72
|
-
* @returns The retired generation.
|
|
73
|
-
*/
|
|
74
|
-
replace(generation) {
|
|
75
|
-
if (this.#generation === undefined) {
|
|
76
|
-
throw new Error("Environment generation is not current.");
|
|
77
|
-
}
|
|
78
|
-
const previous = this.#generation;
|
|
79
|
-
this.#generation = generation;
|
|
80
|
-
return previous;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Creates a registration claim in the current generation.
|
|
84
|
-
*
|
|
85
|
-
* @param ownership Selects caller or server ownership.
|
|
86
|
-
* @returns The new opaque registration claim.
|
|
87
|
-
*/
|
|
88
|
-
claim(ownership) {
|
|
89
|
-
if (this.#ownership !== undefined && this.#ownership !== ownership) {
|
|
90
|
-
throw new Error("Server-owned environment registration requires exclusive ownership.");
|
|
91
|
-
}
|
|
92
|
-
this.#generation ??= Object.freeze({ generation: true });
|
|
93
|
-
this.#ownership ??= ownership;
|
|
94
|
-
this.#nextToken += 1;
|
|
95
|
-
const claim = Object.freeze({
|
|
96
|
-
token: `registration-${this.#nextToken.toString()}`,
|
|
97
|
-
generation: this.#generation,
|
|
98
|
-
});
|
|
99
|
-
this.#claims.set(claim.token, claim);
|
|
100
|
-
return claim;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Removes one failed attachment without disturbing sibling claims.
|
|
104
|
-
*
|
|
105
|
-
* @internal
|
|
106
|
-
* @param token Identifies the claim.
|
|
107
|
-
* @returns The remaining claim count.
|
|
108
|
-
*/
|
|
109
|
-
remove(token) {
|
|
110
|
-
if (!this.#claims.delete(token)) {
|
|
111
|
-
throw new Error("Environment registration is not active.");
|
|
112
|
-
}
|
|
113
|
-
return this.#claims.size;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Clears an empty generation after its retirement is safe.
|
|
117
|
-
*
|
|
118
|
-
* @internal
|
|
119
|
-
* @param generation Identifies the generation to clear.
|
|
120
|
-
*/
|
|
121
|
-
clear(generation) {
|
|
122
|
-
if (this.#claims.size > 0) {
|
|
123
|
-
throw new Error("Environment generation still has live registrations.");
|
|
124
|
-
}
|
|
125
|
-
if (this.#generation !== generation) {
|
|
126
|
-
throw new Error("Environment generation is not current.");
|
|
127
|
-
}
|
|
128
|
-
this.#generation = undefined;
|
|
129
|
-
this.#ownership = undefined;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Coordinates generation attachment, detachment, recovery, and retirement for one environment.
|
|
134
|
-
*
|
|
135
|
-
* @internal
|
|
136
|
-
*/
|
|
137
|
-
export class EnvironmentAttachments {
|
|
138
|
-
#registrations = new EnvironmentRegistrations();
|
|
139
|
-
#generations = new Map();
|
|
140
|
-
#createWorker;
|
|
141
|
-
#logger;
|
|
142
|
-
#deliveryPorts;
|
|
143
|
-
#report;
|
|
144
|
-
#transitionFaults;
|
|
145
|
-
#handles = new WeakMap();
|
|
146
|
-
#attached = new Map();
|
|
147
|
-
#nonLastDetachTokens = new Set();
|
|
148
|
-
#serial = Promise.resolve();
|
|
149
|
-
#failedRollback;
|
|
150
|
-
#failedLastDetach;
|
|
151
|
-
#stop;
|
|
152
|
-
#permanentCloseAdmission;
|
|
153
|
-
#permanentlyClosed = false;
|
|
154
|
-
/**
|
|
155
|
-
* Creates an attachment owner.
|
|
156
|
-
* @param options Optionally supplies lifecycle test seams.
|
|
157
|
-
*/
|
|
158
|
-
constructor(options = {}) {
|
|
159
|
-
this.#createWorker = options.createWorker ?? (() => new EnvironmentDeliveryWorker());
|
|
160
|
-
this.#logger = options.logger;
|
|
161
|
-
this.#deliveryPorts = options.deliveryPorts ?? (() => undefined);
|
|
162
|
-
this.#report = options.report ?? (() => Promise.resolve());
|
|
163
|
-
this.#transitionFaults = options.transitionFaults;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Returns the number of bounded unresolved diagnostics.
|
|
167
|
-
*
|
|
168
|
-
* @internal
|
|
169
|
-
* @returns The unresolved count.
|
|
170
|
-
*/
|
|
171
|
-
get unresolvedReportedDomainCount() {
|
|
172
|
-
let count = 0;
|
|
173
|
-
for (const generation of this.#generations.values()) {
|
|
174
|
-
count += generation.unresolvedReportedDomainCount;
|
|
175
|
-
}
|
|
176
|
-
return count;
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Returns the number of live registrations.
|
|
180
|
-
*
|
|
181
|
-
* @internal
|
|
182
|
-
* @returns The active registration count.
|
|
183
|
-
*/
|
|
184
|
-
get activeRegistrationCount() {
|
|
185
|
-
return this.#registrations.count;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Returns the number of configured delivery owners.
|
|
189
|
-
*
|
|
190
|
-
* @internal
|
|
191
|
-
* @returns The configured owner count.
|
|
192
|
-
*/
|
|
193
|
-
get configuredOwnerCount() {
|
|
194
|
-
let count = 0;
|
|
195
|
-
for (const generation of this.#generations.values()) {
|
|
196
|
-
count += generation.configuredOwnerCount;
|
|
197
|
-
}
|
|
198
|
-
return count;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Returns the number of configured delivery scopes.
|
|
202
|
-
*
|
|
203
|
-
* @internal
|
|
204
|
-
* @returns The configured scope count.
|
|
205
|
-
*/
|
|
206
|
-
get configuredScopeCount() {
|
|
207
|
-
let count = 0;
|
|
208
|
-
for (const generation of this.#generations.values()) {
|
|
209
|
-
count += generation.configuredScopeCount;
|
|
210
|
-
}
|
|
211
|
-
return count;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Checks whether a failed attachment owns unfinished rollback.
|
|
215
|
-
*
|
|
216
|
-
* @internal
|
|
217
|
-
* @returns Whether rollback remains.
|
|
218
|
-
*/
|
|
219
|
-
get failedStartPending() {
|
|
220
|
-
return this.#failedRollback !== undefined;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Checks whether a rejection owns failed-start rollback retry.
|
|
224
|
-
*
|
|
225
|
-
* @internal
|
|
226
|
-
* @param error Supplies the rejection.
|
|
227
|
-
* @returns Whether retry is required.
|
|
228
|
-
*/
|
|
229
|
-
failedStartRetryPending(error) {
|
|
230
|
-
const rollback = this.#failedRollback;
|
|
231
|
-
return rollback?.rejection !== undefined && rollback.rejection === error;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Checks whether detachment made an endpoint safe.
|
|
235
|
-
*
|
|
236
|
-
* @internal
|
|
237
|
-
* @param attachment Identifies the attachment.
|
|
238
|
-
* @returns Whether the endpoint is safe.
|
|
239
|
-
*/
|
|
240
|
-
endpointSafe(attachment) {
|
|
241
|
-
const attached = this.#handles.get(attachment);
|
|
242
|
-
if (attached === undefined) {
|
|
243
|
-
throw new Error("Environment attachment handle is not owned by this environment.");
|
|
244
|
-
}
|
|
245
|
-
if (attached.detachKind === "last") {
|
|
246
|
-
return attached.generation.replacementSafe;
|
|
247
|
-
}
|
|
248
|
-
if (attached.detachKind === "non-last") {
|
|
249
|
-
return attached.generation.registrationEndpointSafe(attached.claim.token);
|
|
250
|
-
}
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Checks whether an attachment owns rejected detach work.
|
|
255
|
-
*
|
|
256
|
-
* @internal
|
|
257
|
-
* @param attachment Identifies the attachment.
|
|
258
|
-
* @returns Whether detach retry is pending.
|
|
259
|
-
*/
|
|
260
|
-
detachRetryPending(attachment) {
|
|
261
|
-
const attached = this.#handles.get(attachment);
|
|
262
|
-
if (attached === undefined) {
|
|
263
|
-
throw new Error("Environment attachment handle is not owned by this environment.");
|
|
264
|
-
}
|
|
265
|
-
return attached.operation?.status === "rejected";
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Attaches configured descriptors to an environment generation.
|
|
269
|
-
*
|
|
270
|
-
* @param options Supplies ownership and descriptors.
|
|
271
|
-
* @returns The successful attachment handle.
|
|
272
|
-
*/
|
|
273
|
-
attach(options) {
|
|
274
|
-
if (this.#permanentlyClosed) {
|
|
275
|
-
return Promise.reject(EnvironmentAttachmentAssembly.environmentClosedError());
|
|
276
|
-
}
|
|
277
|
-
const gate = Promise.withResolvers();
|
|
278
|
-
const stop = this.#stop;
|
|
279
|
-
const waitForStop = stop !== undefined && !stop.completed;
|
|
280
|
-
const snapshotDescriptors = !waitForStop && this.#permanentCloseAdmission === undefined;
|
|
281
|
-
const waiter = {
|
|
282
|
-
options: Object.freeze({
|
|
283
|
-
ownership: options.ownership,
|
|
284
|
-
descriptors: snapshotDescriptors
|
|
285
|
-
? Object.freeze([...options.descriptors])
|
|
286
|
-
: options.descriptors,
|
|
287
|
-
}),
|
|
288
|
-
gate,
|
|
289
|
-
snapshotDescriptors,
|
|
290
|
-
status: waitForStop ? "waiting" : "queued",
|
|
291
|
-
};
|
|
292
|
-
if (waitForStop) {
|
|
293
|
-
stop.waiters.push(waiter);
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
this.#queueAttachment(waiter);
|
|
297
|
-
}
|
|
298
|
-
return gate.promise;
|
|
299
|
-
}
|
|
300
|
-
#queueAttachment(waiter) {
|
|
301
|
-
waiter.status = "queued";
|
|
302
|
-
const attaching = this.#serial.then(async () => {
|
|
303
|
-
if (this.#permanentlyClosed) {
|
|
304
|
-
throw EnvironmentAttachmentAssembly.environmentClosedError();
|
|
305
|
-
}
|
|
306
|
-
const admittedOptions = Object.freeze({
|
|
307
|
-
ownership: waiter.options.ownership,
|
|
308
|
-
descriptors: waiter.snapshotDescriptors
|
|
309
|
-
? waiter.options.descriptors
|
|
310
|
-
: Object.freeze([...waiter.options.descriptors]),
|
|
311
|
-
});
|
|
312
|
-
const attachment = await this.#startQueuedAttachment(admittedOptions);
|
|
313
|
-
waiter.status = "complete";
|
|
314
|
-
waiter.gate.resolve(attachment);
|
|
315
|
-
});
|
|
316
|
-
// spine-log-boundary: server.attachment_queue_serial_tail
|
|
317
|
-
this.#serial = attaching.then(() => undefined, () => undefined);
|
|
318
|
-
// spine-log-boundary: server.attachment_queued_gate
|
|
319
|
-
void attaching.catch((error) => {
|
|
320
|
-
waiter.status = "complete";
|
|
321
|
-
waiter.gate.reject(error);
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
async #releaseStopWaiters(stop) {
|
|
325
|
-
if (!stop.waitersReleased) {
|
|
326
|
-
stop.waitersReleased = true;
|
|
327
|
-
const settlements = stop.waiters.map((waiter) => {
|
|
328
|
-
this.#queueAttachment(waiter);
|
|
329
|
-
return waiter.gate.promise;
|
|
330
|
-
});
|
|
331
|
-
stop.waiters.length = 0;
|
|
332
|
-
stop.waiterSettlement = Promise.allSettled(settlements).then(() => undefined);
|
|
333
|
-
}
|
|
334
|
-
await stop.waiterSettlement;
|
|
335
|
-
}
|
|
336
|
-
async #startQueuedAttachment(options) {
|
|
337
|
-
if (this.#permanentlyClosed) {
|
|
338
|
-
throw EnvironmentAttachmentAssembly.environmentClosedError();
|
|
339
|
-
}
|
|
340
|
-
if (this.#failedRollback !== undefined) {
|
|
341
|
-
throw EnvironmentAttachmentValues.explicitRetryError();
|
|
342
|
-
}
|
|
343
|
-
if (this.#failedLastDetach !== undefined) {
|
|
344
|
-
throw EnvironmentAttachmentValues.detachRetryRequiredError();
|
|
345
|
-
}
|
|
346
|
-
const claim = this.#registrations.claim(options.ownership);
|
|
347
|
-
let generation = this.#generations.get(claim.generation);
|
|
348
|
-
if (generation === undefined) {
|
|
349
|
-
try {
|
|
350
|
-
generation = new DeliveryGeneration(this.#createWorker(), this.#report, this.#deliveryPorts(), this.#logger);
|
|
351
|
-
}
|
|
352
|
-
catch (error) {
|
|
353
|
-
if (this.#registrations.remove(claim.token) === 0) {
|
|
354
|
-
this.#registrations.clear(claim.generation);
|
|
355
|
-
}
|
|
356
|
-
throw error;
|
|
357
|
-
}
|
|
358
|
-
this.#generations.set(claim.generation, generation);
|
|
359
|
-
}
|
|
360
|
-
const binding = { generation: claim.generation };
|
|
361
|
-
const attachment = await this.#attachRegistration(generation, claim, options.descriptors, binding);
|
|
362
|
-
this.#handles.set(attachment, {
|
|
363
|
-
claim,
|
|
364
|
-
generation,
|
|
365
|
-
binding,
|
|
366
|
-
descriptors: options.descriptors,
|
|
367
|
-
operation: undefined,
|
|
368
|
-
claimRemoved: false,
|
|
369
|
-
detachKind: undefined,
|
|
370
|
-
generationCleared: false,
|
|
371
|
-
});
|
|
372
|
-
const handle = this.#handles.get(attachment);
|
|
373
|
-
if (handle === undefined) {
|
|
374
|
-
throw new Error("Attached environment handle was not retained.");
|
|
375
|
-
}
|
|
376
|
-
this.#attached.set(claim.token, handle);
|
|
377
|
-
return attachment;
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Stops delivery work while preserving a retryable failed stop.
|
|
381
|
-
*
|
|
382
|
-
* @returns A promise that settles after delivery work stops.
|
|
383
|
-
*/
|
|
384
|
-
stopDelivery() {
|
|
385
|
-
if (this.#permanentlyClosed) {
|
|
386
|
-
return Promise.reject(EnvironmentAttachmentAssembly.environmentClosedError());
|
|
387
|
-
}
|
|
388
|
-
const current = this.#stop;
|
|
389
|
-
if (current !== undefined) {
|
|
390
|
-
if (current.status === "rejected") {
|
|
391
|
-
return Promise.reject(EnvironmentAttachmentValues.deliveryStopRetryError());
|
|
392
|
-
}
|
|
393
|
-
return current.promise;
|
|
394
|
-
}
|
|
395
|
-
const stop = {
|
|
396
|
-
admitted: false,
|
|
397
|
-
generation: undefined,
|
|
398
|
-
old: undefined,
|
|
399
|
-
survivors: undefined,
|
|
400
|
-
routes: undefined,
|
|
401
|
-
oldRetired: false,
|
|
402
|
-
oldRetirement: { status: "pending" },
|
|
403
|
-
candidateGeneration: undefined,
|
|
404
|
-
candidate: undefined,
|
|
405
|
-
routeUnits: undefined,
|
|
406
|
-
routeSnapshots: undefined,
|
|
407
|
-
routePrepared: 0,
|
|
408
|
-
scopes: new TransitionScopes(),
|
|
409
|
-
published: false,
|
|
410
|
-
reopened: undefined,
|
|
411
|
-
drained: false,
|
|
412
|
-
waiters: [],
|
|
413
|
-
waitersReleased: false,
|
|
414
|
-
waiterSettlement: undefined,
|
|
415
|
-
completed: false,
|
|
416
|
-
cancelledByClose: undefined,
|
|
417
|
-
promise: Promise.resolve(),
|
|
418
|
-
status: "running",
|
|
419
|
-
retrying: false,
|
|
420
|
-
};
|
|
421
|
-
this.#stop = stop;
|
|
422
|
-
return this.#queueStop(stop, false);
|
|
423
|
-
}
|
|
424
|
-
#queueStop(stop, retrying) {
|
|
425
|
-
stop.status = "running";
|
|
426
|
-
stop.retrying = retrying;
|
|
427
|
-
const gate = Promise.withResolvers();
|
|
428
|
-
stop.promise = gate.promise;
|
|
429
|
-
const admission = this.#serial.then(() => {
|
|
430
|
-
void this.#continueStop(stop).then(() => {
|
|
431
|
-
gate.resolve(undefined);
|
|
432
|
-
}, (reason) => {
|
|
433
|
-
gate.reject(reason);
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
// spine-log-boundary: server.attachment_stop_serial_tail
|
|
437
|
-
this.#serial = admission.then(() => undefined, () => undefined);
|
|
438
|
-
// spine-log-boundary: server.attachment_stop_admission_gate
|
|
439
|
-
void admission.catch(gate.reject);
|
|
440
|
-
void stop.promise.then(() => {
|
|
441
|
-
stop.status = "complete";
|
|
442
|
-
if (this.#stop === stop) {
|
|
443
|
-
this.#stop = undefined;
|
|
444
|
-
}
|
|
445
|
-
}, () => {
|
|
446
|
-
if (stop.completed) {
|
|
447
|
-
stop.status = "complete";
|
|
448
|
-
if (this.#stop === stop) {
|
|
449
|
-
this.#stop = undefined;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
stop.status = "rejected";
|
|
454
|
-
}
|
|
455
|
-
});
|
|
456
|
-
return stop.promise;
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Retries a previously rejected delivery stop.
|
|
460
|
-
*
|
|
461
|
-
* @returns A promise that settles after the stop retry completes.
|
|
462
|
-
*/
|
|
463
|
-
retryDeliveryStop() {
|
|
464
|
-
if (this.#permanentlyClosed) {
|
|
465
|
-
return Promise.reject(EnvironmentAttachmentAssembly.environmentClosedError());
|
|
466
|
-
}
|
|
467
|
-
const stop = this.#stop;
|
|
468
|
-
if (stop?.status === "running" && stop.retrying) {
|
|
469
|
-
return stop.promise;
|
|
470
|
-
}
|
|
471
|
-
if (stop?.status !== "rejected") {
|
|
472
|
-
return Promise.reject(new Error("Environment has no failed delivery stop to retry."));
|
|
473
|
-
}
|
|
474
|
-
return this.#queueStop(stop, true);
|
|
475
|
-
}
|
|
476
|
-
async #continueStop(stop) {
|
|
477
|
-
if (stop.cancelledByClose !== undefined) {
|
|
478
|
-
stop.completed = true;
|
|
479
|
-
await stop.waiterSettlement;
|
|
480
|
-
throw stop.cancelledByClose;
|
|
481
|
-
}
|
|
482
|
-
if (!stop.admitted) {
|
|
483
|
-
if (this.#failedRollback !== undefined) {
|
|
484
|
-
await this.#refuseStop(stop, EnvironmentAttachmentValues.explicitRetryError());
|
|
485
|
-
}
|
|
486
|
-
if (this.#failedLastDetach !== undefined) {
|
|
487
|
-
await this.#refuseStop(stop, EnvironmentAttachmentValues.detachRetryRequiredError());
|
|
488
|
-
}
|
|
489
|
-
if (this.#failedNonLastDetachOwnsRegistration()) {
|
|
490
|
-
await this.#refuseStop(stop, EnvironmentAttachmentValues.detachRetryRequiredError());
|
|
491
|
-
}
|
|
492
|
-
const generation = this.#registrations.generation;
|
|
493
|
-
if (generation === undefined) {
|
|
494
|
-
stop.completed = true;
|
|
495
|
-
await this.#releaseStopWaiters(stop);
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
const old = this.#generations.get(generation);
|
|
499
|
-
if (old === undefined) {
|
|
500
|
-
await this.#refuseStop(stop, new Error("Environment generation is not current."));
|
|
501
|
-
}
|
|
502
|
-
stop.generation = generation;
|
|
503
|
-
stop.old = old;
|
|
504
|
-
stop.survivors = Object.freeze([...this.#attached.values()]);
|
|
505
|
-
stop.admitted = true;
|
|
506
|
-
}
|
|
507
|
-
const { old, survivors } = stop;
|
|
508
|
-
if (old === undefined || survivors === undefined) {
|
|
509
|
-
throw new Error("Admitted delivery stop is missing generation state.");
|
|
510
|
-
}
|
|
511
|
-
if (stop.routes === undefined) {
|
|
512
|
-
stop.routes = Object.freeze(survivors.map((survivor) => old.closeRegistration(survivor.claim.token, (descriptor, ready) => {
|
|
513
|
-
stop.scopes.buffer(survivor.claim.token, descriptor, ready);
|
|
514
|
-
})));
|
|
515
|
-
old.captureTransition(stop.scopes);
|
|
516
|
-
}
|
|
517
|
-
if (!stop.oldRetired) {
|
|
518
|
-
const retirement = await old.retire();
|
|
519
|
-
if (retirement.status === "failed") {
|
|
520
|
-
if (!old.replacementSafe) {
|
|
521
|
-
throw retirement.reason;
|
|
522
|
-
}
|
|
523
|
-
stop.oldRetirement = {
|
|
524
|
-
status: "retained",
|
|
525
|
-
reason: retirement.reason,
|
|
526
|
-
causes: retirement.causes,
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
stop.oldRetired = true;
|
|
530
|
-
}
|
|
531
|
-
try {
|
|
532
|
-
let routeUnits = stop.routeUnits;
|
|
533
|
-
if (routeUnits === undefined) {
|
|
534
|
-
const routes = stop.routes;
|
|
535
|
-
routeUnits = Object.freeze(survivors.flatMap((survivor, registration) => {
|
|
536
|
-
const readiness = routes.at(registration);
|
|
537
|
-
if (readiness === undefined) {
|
|
538
|
-
throw new Error("Closed delivery route is missing readiness.");
|
|
539
|
-
}
|
|
540
|
-
return survivor.descriptors.map((descriptor) => ({
|
|
541
|
-
survivor,
|
|
542
|
-
descriptor,
|
|
543
|
-
readiness,
|
|
544
|
-
}));
|
|
545
|
-
}));
|
|
546
|
-
stop.routeUnits = routeUnits;
|
|
547
|
-
}
|
|
548
|
-
if (stop.routeSnapshots === undefined) {
|
|
549
|
-
const snapshots = [];
|
|
550
|
-
for (const route of routeUnits) {
|
|
551
|
-
snapshots.push(await EnvironmentAttachmentAssembly.snapshotDescriptor(route.descriptor));
|
|
552
|
-
}
|
|
553
|
-
stop.routeSnapshots = Object.freeze(snapshots);
|
|
554
|
-
}
|
|
555
|
-
if (stop.candidate === undefined) {
|
|
556
|
-
const candidate = new DeliveryGeneration(this.#createWorker(), this.#report, this.#deliveryPorts(), this.#logger);
|
|
557
|
-
stop.candidateGeneration = Object.freeze({ generation: true });
|
|
558
|
-
stop.candidate = candidate;
|
|
559
|
-
}
|
|
560
|
-
const candidate = stop.candidate;
|
|
561
|
-
const candidateGeneration = stop.candidateGeneration;
|
|
562
|
-
if (candidateGeneration === undefined) {
|
|
563
|
-
throw new Error("Candidate delivery generation was not retained.");
|
|
564
|
-
}
|
|
565
|
-
while (stop.routePrepared < routeUnits.length) {
|
|
566
|
-
const route = routeUnits[stop.routePrepared];
|
|
567
|
-
const snapshot = stop.routeSnapshots[stop.routePrepared];
|
|
568
|
-
if (route === undefined || snapshot === undefined) {
|
|
569
|
-
throw new Error("Prepared delivery route is missing its snapshot.");
|
|
570
|
-
}
|
|
571
|
-
this.#transitionFaults?.onRoutePrepare?.(route.descriptor);
|
|
572
|
-
const scopes = await candidate.prepareTransferredRoute(Object.freeze({ token: route.survivor.claim.token, generation: candidateGeneration }), snapshot, route.readiness);
|
|
573
|
-
stop.scopes.capture(route.survivor.claim.token, route.descriptor, scopes, "startup");
|
|
574
|
-
stop.routePrepared += 1;
|
|
575
|
-
}
|
|
576
|
-
let transfer = stop.scopes.nextPending();
|
|
577
|
-
while (transfer !== undefined) {
|
|
578
|
-
const version = stop.scopes.begin(transfer);
|
|
579
|
-
const settling = candidate.recoverTransferred(transfer.token, transfer.descriptor, transfer.ready);
|
|
580
|
-
let fault = { status: "none" };
|
|
581
|
-
try {
|
|
582
|
-
this.#transitionFaults?.onScopeTransfer?.(transfer.descriptor, stop.scopes.sources(transfer));
|
|
583
|
-
}
|
|
584
|
-
catch (error) {
|
|
585
|
-
fault = { status: "retained", reason: error };
|
|
586
|
-
}
|
|
587
|
-
const failures = [];
|
|
588
|
-
try {
|
|
589
|
-
await settling;
|
|
590
|
-
}
|
|
591
|
-
catch (error) {
|
|
592
|
-
failures.push(error);
|
|
593
|
-
}
|
|
594
|
-
if (fault.status === "retained") {
|
|
595
|
-
failures.push(fault.reason);
|
|
596
|
-
}
|
|
597
|
-
if (failures.length > 0) {
|
|
598
|
-
stop.scopes.reject(transfer);
|
|
599
|
-
EnvironmentAttachmentValues.throwCurrentAggregation(failures);
|
|
600
|
-
}
|
|
601
|
-
stop.scopes.complete(transfer, version);
|
|
602
|
-
transfer = stop.scopes.nextPending();
|
|
603
|
-
}
|
|
604
|
-
if (!stop.published) {
|
|
605
|
-
const generation = stop.generation;
|
|
606
|
-
if (generation === undefined) {
|
|
607
|
-
throw new Error("Admitted delivery stop is missing its generation.");
|
|
608
|
-
}
|
|
609
|
-
this.#generations.delete(generation);
|
|
610
|
-
this.#generations.set(candidateGeneration, candidate);
|
|
611
|
-
this.#registrations.replace(candidateGeneration);
|
|
612
|
-
for (const survivor of survivors) {
|
|
613
|
-
survivor.generation = candidate;
|
|
614
|
-
survivor.binding.generation = candidateGeneration;
|
|
615
|
-
}
|
|
616
|
-
stop.published = true;
|
|
617
|
-
}
|
|
618
|
-
if (stop.reopened === undefined) {
|
|
619
|
-
const reopened = [];
|
|
620
|
-
for (const survivor of survivors) {
|
|
621
|
-
reopened.push(...candidate.openRegistration(survivor.claim.token));
|
|
622
|
-
}
|
|
623
|
-
stop.reopened = Object.freeze(reopened);
|
|
624
|
-
}
|
|
625
|
-
if (!stop.drained) {
|
|
626
|
-
await candidate.recoverReopened(stop.reopened);
|
|
627
|
-
stop.drained = true;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
catch (error) {
|
|
631
|
-
const failures = [
|
|
632
|
-
...this.#takeOldRetirementCauses(stop),
|
|
633
|
-
...EnvironmentAttachmentValues.currentAggregationCauses(error),
|
|
634
|
-
];
|
|
635
|
-
EnvironmentAttachmentValues.throwFailures(failures, "Environment delivery stop transition failed.");
|
|
636
|
-
}
|
|
637
|
-
stop.completed = true;
|
|
638
|
-
await this.#releaseStopWaiters(stop);
|
|
639
|
-
if (stop.oldRetirement.status === "retained") {
|
|
640
|
-
const { reason } = stop.oldRetirement;
|
|
641
|
-
stop.oldRetirement = { status: "emitted" };
|
|
642
|
-
throw reason;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
#takeOldRetirementCauses(stop) {
|
|
646
|
-
if (stop.oldRetirement.status !== "retained") {
|
|
647
|
-
return Object.freeze([]);
|
|
648
|
-
}
|
|
649
|
-
const { causes } = stop.oldRetirement;
|
|
650
|
-
stop.oldRetirement = { status: "emitted" };
|
|
651
|
-
return causes;
|
|
652
|
-
}
|
|
653
|
-
async #refuseStop(stop, error) {
|
|
654
|
-
stop.completed = true;
|
|
655
|
-
await this.#releaseStopWaiters(stop);
|
|
656
|
-
throw error;
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* Accepts permanent close after all active registrations retire.
|
|
660
|
-
*
|
|
661
|
-
* @internal
|
|
662
|
-
* @returns A promise that resolves after permanent close is admitted.
|
|
663
|
-
*/
|
|
664
|
-
admitPermanentClose() {
|
|
665
|
-
if (this.#permanentlyClosed) {
|
|
666
|
-
return Promise.resolve();
|
|
667
|
-
}
|
|
668
|
-
const current = this.#permanentCloseAdmission;
|
|
669
|
-
if (current !== undefined) {
|
|
670
|
-
return current;
|
|
671
|
-
}
|
|
672
|
-
const admission = this.#serial.then(() => {
|
|
673
|
-
if (this.#registrations.count !== 0) {
|
|
674
|
-
throw new Error("ServerEnvironment cannot close while it is in use.");
|
|
675
|
-
}
|
|
676
|
-
if (this.#failedRollback !== undefined) {
|
|
677
|
-
throw EnvironmentAttachmentValues.explicitRetryError();
|
|
678
|
-
}
|
|
679
|
-
this.#cancelProvisionalStop();
|
|
680
|
-
if (this.#registrations.generation !== undefined || this.#generations.size !== 0) {
|
|
681
|
-
throw new Error("Environment generation is not current.");
|
|
682
|
-
}
|
|
683
|
-
this.#permanentlyClosed = true;
|
|
684
|
-
});
|
|
685
|
-
this.#permanentCloseAdmission = admission;
|
|
686
|
-
// spine-log-boundary: server.attachment_permanent_close_serial_tail
|
|
687
|
-
this.#serial = admission.then(() => undefined, () => undefined);
|
|
688
|
-
const clearAdmission = () => {
|
|
689
|
-
if (this.#permanentCloseAdmission === admission) {
|
|
690
|
-
this.#permanentCloseAdmission = undefined;
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
void admission.then(clearAdmission, clearAdmission);
|
|
694
|
-
return admission;
|
|
695
|
-
}
|
|
696
|
-
#cancelProvisionalStop() {
|
|
697
|
-
const stop = this.#stop;
|
|
698
|
-
if (stop === undefined || stop.admitted || stop.completed) {
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
const error = EnvironmentAttachmentAssembly.environmentClosedError();
|
|
702
|
-
stop.cancelledByClose = error;
|
|
703
|
-
stop.waitersReleased = true;
|
|
704
|
-
const waiters = [...stop.waiters];
|
|
705
|
-
stop.waiters.length = 0;
|
|
706
|
-
stop.waiterSettlement = Promise.allSettled(waiters.map((waiter) => waiter.gate.promise)).then(() => undefined);
|
|
707
|
-
for (const waiter of waiters) {
|
|
708
|
-
waiter.status = "complete";
|
|
709
|
-
waiter.gate.reject(error);
|
|
710
|
-
}
|
|
711
|
-
if (this.#stop === stop) {
|
|
712
|
-
this.#stop = undefined;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* Removes one attached registration.
|
|
717
|
-
*
|
|
718
|
-
* @param attachment Identifies the attachment to detach.
|
|
719
|
-
* @returns A promise that settles after the attachment detaches.
|
|
720
|
-
*/
|
|
721
|
-
detach(attachment) {
|
|
722
|
-
const attached = this.#handles.get(attachment);
|
|
723
|
-
if (attached === undefined) {
|
|
724
|
-
return Promise.reject(new Error("Environment attachment handle is not owned by this environment."));
|
|
725
|
-
}
|
|
726
|
-
if (attached.operation !== undefined) {
|
|
727
|
-
return attached.operation.promise;
|
|
728
|
-
}
|
|
729
|
-
if (this.#rejectedStopOwns(attached)) {
|
|
730
|
-
return Promise.reject(EnvironmentAttachmentValues.deliveryStopRetryError());
|
|
731
|
-
}
|
|
732
|
-
if (this.#failedRollback !== undefined) {
|
|
733
|
-
return Promise.reject(EnvironmentAttachmentValues.explicitRetryError());
|
|
734
|
-
}
|
|
735
|
-
return this.#queueDetach(attached);
|
|
736
|
-
}
|
|
737
|
-
/**
|
|
738
|
-
* Retries rejected detachment for one attachment.
|
|
739
|
-
*
|
|
740
|
-
* @param attachment Identifies the attachment to retry.
|
|
741
|
-
* @returns A promise that settles after the detach retry completes.
|
|
742
|
-
*/
|
|
743
|
-
retryDetach(attachment) {
|
|
744
|
-
const attached = this.#handles.get(attachment);
|
|
745
|
-
if (attached === undefined) {
|
|
746
|
-
return Promise.reject(new Error("Environment attachment handle is not owned by this environment."));
|
|
747
|
-
}
|
|
748
|
-
if (this.#rejectedStopOwns(attached)) {
|
|
749
|
-
return Promise.reject(EnvironmentAttachmentValues.deliveryStopRetryError());
|
|
750
|
-
}
|
|
751
|
-
const operation = attached.operation;
|
|
752
|
-
if (operation === undefined) {
|
|
753
|
-
return Promise.reject(new Error("Environment attachment has no failed detach to retry."));
|
|
754
|
-
}
|
|
755
|
-
if (operation.status === "running") {
|
|
756
|
-
return Promise.reject(new Error("Environment attachment detach has not rejected."));
|
|
757
|
-
}
|
|
758
|
-
if (operation.status === "complete" ||
|
|
759
|
-
!attached.generation.hasRegistration(attached.claim.token)) {
|
|
760
|
-
operation.status = "complete";
|
|
761
|
-
return Promise.resolve();
|
|
762
|
-
}
|
|
763
|
-
if (this.#failedRollback !== undefined) {
|
|
764
|
-
return Promise.reject(EnvironmentAttachmentValues.explicitRetryError());
|
|
765
|
-
}
|
|
766
|
-
return this.#queueDetach(attached, operation);
|
|
767
|
-
}
|
|
768
|
-
#queueDetach(attached, previousOperation) {
|
|
769
|
-
const detaching = this.#serial.then(() => {
|
|
770
|
-
if (this.#incompleteStopOwns(attached)) {
|
|
771
|
-
throw EnvironmentAttachmentValues.deliveryStopRetryError();
|
|
772
|
-
}
|
|
773
|
-
if (this.#failedRollback !== undefined) {
|
|
774
|
-
attached.operation = previousOperation;
|
|
775
|
-
throw EnvironmentAttachmentValues.explicitRetryError();
|
|
776
|
-
}
|
|
777
|
-
return this.#detachRegistration(attached);
|
|
778
|
-
});
|
|
779
|
-
const operation = { promise: detaching, status: "running" };
|
|
780
|
-
attached.operation = operation;
|
|
781
|
-
// spine-log-boundary: server.attachment_detach_serial_tail
|
|
782
|
-
this.#serial = detaching.then(() => undefined, () => undefined);
|
|
783
|
-
void detaching.then(() => {
|
|
784
|
-
operation.status = "complete";
|
|
785
|
-
}, () => {
|
|
786
|
-
operation.status = "rejected";
|
|
787
|
-
});
|
|
788
|
-
return detaching;
|
|
789
|
-
}
|
|
790
|
-
#failedNonLastDetachOwnsRegistration() {
|
|
791
|
-
for (const attached of this.#attached.values()) {
|
|
792
|
-
if (attached.detachKind === "non-last" &&
|
|
793
|
-
attached.operation?.status !== "complete" &&
|
|
794
|
-
attached.generation.hasRegistration(attached.claim.token)) {
|
|
795
|
-
return true;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
return false;
|
|
799
|
-
}
|
|
800
|
-
#rejectedStopOwns(attached) {
|
|
801
|
-
const stop = this.#stop;
|
|
802
|
-
return (stop?.status === "rejected" && stop.admitted && stop.survivors?.includes(attached) === true);
|
|
803
|
-
}
|
|
804
|
-
#incompleteStopOwns(attached) {
|
|
805
|
-
const stop = this.#stop;
|
|
806
|
-
return stop?.admitted === true && !stop.drained && stop.survivors?.includes(attached) === true;
|
|
807
|
-
}
|
|
808
|
-
async #detachRegistration(attached) {
|
|
809
|
-
if (attached.detachKind === undefined) {
|
|
810
|
-
const liveRegistrations = this.#registrations.count - this.#nonLastDetachTokens.size;
|
|
811
|
-
if (this.#registrations.count === 1) {
|
|
812
|
-
attached.detachKind = "last";
|
|
813
|
-
}
|
|
814
|
-
else if (liveRegistrations <= 1) {
|
|
815
|
-
throw new Error("Environment attachment cannot detach the reserved live registration.");
|
|
816
|
-
}
|
|
817
|
-
else {
|
|
818
|
-
attached.detachKind = "non-last";
|
|
819
|
-
this.#nonLastDetachTokens.add(attached.claim.token);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
if (attached.detachKind === "last") {
|
|
823
|
-
try {
|
|
824
|
-
await attached.generation.retireRegistration(attached.claim.token);
|
|
825
|
-
}
|
|
826
|
-
catch (error) {
|
|
827
|
-
if (!attached.generation.replacementSafe) {
|
|
828
|
-
this.#failedLastDetach = attached;
|
|
829
|
-
}
|
|
830
|
-
throw error;
|
|
831
|
-
}
|
|
832
|
-
finally {
|
|
833
|
-
if (attached.generation.replacementSafe) {
|
|
834
|
-
this.#clearRetiredGeneration(attached);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
try {
|
|
840
|
-
await attached.generation.detachRegistration(attached.claim.token);
|
|
841
|
-
}
|
|
842
|
-
finally {
|
|
843
|
-
if (!attached.generation.hasRegistration(attached.claim.token) && !attached.claimRemoved) {
|
|
844
|
-
this.#registrations.remove(attached.claim.token);
|
|
845
|
-
this.#attached.delete(attached.claim.token);
|
|
846
|
-
this.#nonLastDetachTokens.delete(attached.claim.token);
|
|
847
|
-
attached.claimRemoved = true;
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
#clearRetiredGeneration(attached) {
|
|
852
|
-
if (!attached.claimRemoved) {
|
|
853
|
-
this.#registrations.remove(attached.claim.token);
|
|
854
|
-
attached.claimRemoved = true;
|
|
855
|
-
}
|
|
856
|
-
if (!attached.generationCleared) {
|
|
857
|
-
this.#generations.delete(attached.binding.generation);
|
|
858
|
-
this.#registrations.clear(attached.binding.generation);
|
|
859
|
-
this.#attached.delete(attached.claim.token);
|
|
860
|
-
attached.generationCleared = true;
|
|
861
|
-
}
|
|
862
|
-
if (this.#failedLastDetach === attached) {
|
|
863
|
-
this.#failedLastDetach = undefined;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
/**
|
|
867
|
-
* Retries rollback following a rejected attachment start.
|
|
868
|
-
*
|
|
869
|
-
* @returns A promise that settles after failed-start rollback retries.
|
|
870
|
-
*/
|
|
871
|
-
retryFailedStart() {
|
|
872
|
-
const rollback = this.#failedRollback;
|
|
873
|
-
if (rollback === undefined) {
|
|
874
|
-
return Promise.reject(new Error("Environment has no failed-start rollback to retry."));
|
|
875
|
-
}
|
|
876
|
-
if (rollback.retry !== undefined) {
|
|
877
|
-
return rollback.retry;
|
|
878
|
-
}
|
|
879
|
-
const retry = this.#serial.then(() => {
|
|
880
|
-
if (this.#registrations.count === 0) {
|
|
881
|
-
rollback.mode = "generation";
|
|
882
|
-
}
|
|
883
|
-
return this.#continueRollback(rollback);
|
|
884
|
-
});
|
|
885
|
-
rollback.retry = retry;
|
|
886
|
-
// spine-log-boundary: server.attachment_rollback_serial_tail
|
|
887
|
-
this.#serial = retry.then(() => undefined, () => undefined);
|
|
888
|
-
// spine-log-boundary: server.attachment_rollback_retry_reset
|
|
889
|
-
void retry.catch(() => {
|
|
890
|
-
if (this.#failedRollback === rollback) {
|
|
891
|
-
rollback.retry = undefined;
|
|
892
|
-
}
|
|
893
|
-
});
|
|
894
|
-
return retry;
|
|
895
|
-
}
|
|
896
|
-
async #attachRegistration(generation, claim, descriptors, binding) {
|
|
897
|
-
try {
|
|
898
|
-
return await generation.attach(claim, descriptors, false, binding);
|
|
899
|
-
}
|
|
900
|
-
catch (startError) {
|
|
901
|
-
const remaining = this.#registrations.remove(claim.token);
|
|
902
|
-
const rollback = {
|
|
903
|
-
claim,
|
|
904
|
-
generation,
|
|
905
|
-
mode: remaining === 0 ? "generation" : "registration",
|
|
906
|
-
inFlight: undefined,
|
|
907
|
-
retry: undefined,
|
|
908
|
-
rejection: undefined,
|
|
909
|
-
};
|
|
910
|
-
this.#failedRollback = rollback;
|
|
911
|
-
try {
|
|
912
|
-
await this.#continueRollback(rollback);
|
|
913
|
-
}
|
|
914
|
-
catch (rollbackError) {
|
|
915
|
-
const rejection = EnvironmentAttachmentValues.failedStartError(startError, rollbackError);
|
|
916
|
-
rollback.rejection = rejection;
|
|
917
|
-
throw rejection;
|
|
918
|
-
}
|
|
919
|
-
throw startError;
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
#continueRollback(rollback) {
|
|
923
|
-
if (rollback.inFlight !== undefined) {
|
|
924
|
-
return rollback.inFlight;
|
|
925
|
-
}
|
|
926
|
-
const gate = Promise.withResolvers();
|
|
927
|
-
rollback.inFlight = gate.promise;
|
|
928
|
-
void this.#performRollback(rollback).then(() => {
|
|
929
|
-
this.#completeRollback(rollback);
|
|
930
|
-
gate.resolve(undefined);
|
|
931
|
-
}, (error) => {
|
|
932
|
-
if (this.#rollbackSafe(rollback)) {
|
|
933
|
-
this.#completeRollback(rollback);
|
|
934
|
-
}
|
|
935
|
-
else {
|
|
936
|
-
rollback.inFlight = undefined;
|
|
937
|
-
}
|
|
938
|
-
gate.reject(error);
|
|
939
|
-
});
|
|
940
|
-
return gate.promise;
|
|
941
|
-
}
|
|
942
|
-
async #performRollback(rollback) {
|
|
943
|
-
if (rollback.mode === "generation") {
|
|
944
|
-
await rollback.generation.retireFailedRegistration(rollback.claim.token);
|
|
945
|
-
}
|
|
946
|
-
else {
|
|
947
|
-
await rollback.generation.rollbackRegistration(rollback.claim.token);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
#rollbackSafe(rollback) {
|
|
951
|
-
return rollback.mode === "generation"
|
|
952
|
-
? rollback.generation.replacementSafe
|
|
953
|
-
: !rollback.generation.hasRegistration(rollback.claim.token);
|
|
954
|
-
}
|
|
955
|
-
#completeRollback(rollback) {
|
|
956
|
-
if (rollback.mode === "generation") {
|
|
957
|
-
this.#generations.delete(rollback.claim.generation);
|
|
958
|
-
this.#registrations.clear(rollback.claim.generation);
|
|
959
|
-
}
|
|
960
|
-
this.#failedRollback = undefined;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
class TransitionScopes {
|
|
964
|
-
#registrations = new Map();
|
|
965
|
-
#pendingHead;
|
|
966
|
-
#pendingTail;
|
|
967
|
-
capture(token, descriptor, scopes, source) {
|
|
968
|
-
for (const scope of scopes) {
|
|
969
|
-
this.#record(token, descriptor, scope.ready, source, false);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
buffer(token, descriptor, ready) {
|
|
973
|
-
this.#record(token, descriptor, ready, "buffered", true);
|
|
974
|
-
}
|
|
975
|
-
nextPending() {
|
|
976
|
-
return this.#pendingHead;
|
|
977
|
-
}
|
|
978
|
-
begin(unit) {
|
|
979
|
-
if (this.#pendingHead !== unit ||
|
|
980
|
-
!unit.queued ||
|
|
981
|
-
unit.inFlightVersion !== undefined ||
|
|
982
|
-
unit.transferredVersion >= unit.version) {
|
|
983
|
-
throw new Error("Environment transition scope is not pending.");
|
|
984
|
-
}
|
|
985
|
-
this.#pendingHead = unit.pendingNext;
|
|
986
|
-
if (this.#pendingHead === undefined) {
|
|
987
|
-
this.#pendingTail = undefined;
|
|
988
|
-
}
|
|
989
|
-
unit.queued = false;
|
|
990
|
-
unit.pendingNext = undefined;
|
|
991
|
-
unit.inFlightVersion = unit.version;
|
|
992
|
-
unit.dirtyDuringFlight = false;
|
|
993
|
-
return unit.inFlightVersion;
|
|
994
|
-
}
|
|
995
|
-
complete(unit, version) {
|
|
996
|
-
if (unit.inFlightVersion !== version) {
|
|
997
|
-
throw new Error("Environment transition scope checkpoint is not current.");
|
|
998
|
-
}
|
|
999
|
-
unit.transferredVersion = version;
|
|
1000
|
-
unit.inFlightVersion = undefined;
|
|
1001
|
-
unit.dirtyDuringFlight = false;
|
|
1002
|
-
if (unit.transferredVersion < unit.version) {
|
|
1003
|
-
this.#enqueue(unit);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
reject(unit) {
|
|
1007
|
-
unit.inFlightVersion = undefined;
|
|
1008
|
-
unit.dirtyDuringFlight = false;
|
|
1009
|
-
this.#enqueue(unit, true);
|
|
1010
|
-
}
|
|
1011
|
-
sources(unit) {
|
|
1012
|
-
return Object.freeze(transitionSourceOrder.filter((source) => unit.provenance.has(source)));
|
|
1013
|
-
}
|
|
1014
|
-
#record(token, descriptor, ready, source, dirty) {
|
|
1015
|
-
const descriptors = this.#registrations.get(token) ??
|
|
1016
|
-
new Map();
|
|
1017
|
-
const scopes = descriptors.get(descriptor) ?? new Map();
|
|
1018
|
-
let unit = scopes.get(EnvironmentAttachmentValues.readyKey(ready));
|
|
1019
|
-
if (unit === undefined) {
|
|
1020
|
-
unit = {
|
|
1021
|
-
token,
|
|
1022
|
-
descriptor,
|
|
1023
|
-
ready,
|
|
1024
|
-
provenance: new Set(),
|
|
1025
|
-
version: 1,
|
|
1026
|
-
transferredVersion: 0,
|
|
1027
|
-
inFlightVersion: undefined,
|
|
1028
|
-
dirtyDuringFlight: false,
|
|
1029
|
-
queued: false,
|
|
1030
|
-
pendingNext: undefined,
|
|
1031
|
-
};
|
|
1032
|
-
scopes.set(EnvironmentAttachmentValues.readyKey(ready), unit);
|
|
1033
|
-
descriptors.set(descriptor, scopes);
|
|
1034
|
-
this.#registrations.set(token, descriptors);
|
|
1035
|
-
this.#enqueue(unit);
|
|
1036
|
-
}
|
|
1037
|
-
else if (dirty) {
|
|
1038
|
-
if (unit.inFlightVersion !== undefined && !unit.dirtyDuringFlight) {
|
|
1039
|
-
unit.version += 1;
|
|
1040
|
-
unit.dirtyDuringFlight = true;
|
|
1041
|
-
}
|
|
1042
|
-
else if (unit.inFlightVersion === undefined && unit.transferredVersion === unit.version) {
|
|
1043
|
-
unit.version += 1;
|
|
1044
|
-
this.#enqueue(unit);
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
unit.provenance.add(source);
|
|
1048
|
-
}
|
|
1049
|
-
#enqueue(unit, first = false) {
|
|
1050
|
-
if (unit.queued) {
|
|
1051
|
-
return;
|
|
1052
|
-
}
|
|
1053
|
-
unit.queued = true;
|
|
1054
|
-
if (first) {
|
|
1055
|
-
unit.pendingNext = this.#pendingHead;
|
|
1056
|
-
this.#pendingHead = unit;
|
|
1057
|
-
this.#pendingTail ??= unit;
|
|
1058
|
-
}
|
|
1059
|
-
else if (this.#pendingTail === undefined) {
|
|
1060
|
-
unit.pendingNext = undefined;
|
|
1061
|
-
this.#pendingHead = unit;
|
|
1062
|
-
this.#pendingTail = unit;
|
|
1063
|
-
}
|
|
1064
|
-
else {
|
|
1065
|
-
unit.pendingNext = undefined;
|
|
1066
|
-
this.#pendingTail.pendingNext = unit;
|
|
1067
|
-
this.#pendingTail = unit;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
const transitionSourceOrder = Object.freeze([
|
|
1072
|
-
"configured",
|
|
1073
|
-
"startup",
|
|
1074
|
-
"buffered",
|
|
1075
|
-
"retained",
|
|
1076
|
-
]);
|
|
1077
|
-
class RegistrationOwnership {
|
|
1078
|
-
#scopes = new Map();
|
|
1079
|
-
#ownerKeys = new Set();
|
|
1080
|
-
add(runtime) {
|
|
1081
|
-
this.#ownerKeys.add(runtime.owner.key);
|
|
1082
|
-
for (const scope of runtime.scopes) {
|
|
1083
|
-
this.#scopes.set(EnvironmentAttachmentValues.scopeKey(scope), scope);
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
get scopes() {
|
|
1087
|
-
return Object.freeze([...this.#scopes.values()]);
|
|
1088
|
-
}
|
|
1089
|
-
get ownerKeys() {
|
|
1090
|
-
return Object.freeze([...this.#ownerKeys]);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
class DeliveryGeneration {
|
|
1094
|
-
#worker;
|
|
1095
|
-
/**
|
|
1096
|
-
* Environment logger child retained for later runtime containment owners.
|
|
1097
|
-
*/
|
|
1098
|
-
logger;
|
|
1099
|
-
#ports;
|
|
1100
|
-
#report;
|
|
1101
|
-
#descriptors = new WeakSet();
|
|
1102
|
-
#runtimes = new WeakMap();
|
|
1103
|
-
#deliveryRecords = new EnvironmentDeliveryRecords();
|
|
1104
|
-
#registrations = new Map();
|
|
1105
|
-
#reportedFailures = new ReportedFailures();
|
|
1106
|
-
#overlapDomains = new Map();
|
|
1107
|
-
#configuredOwners = new Set();
|
|
1108
|
-
#factoryIds = new WeakMap();
|
|
1109
|
-
#nextFactory = 0;
|
|
1110
|
-
#coordinator;
|
|
1111
|
-
#nextOwner = 0;
|
|
1112
|
-
#retiredWithoutCoordinator = false;
|
|
1113
|
-
constructor(worker, report, ports, logger) {
|
|
1114
|
-
this.#worker = worker;
|
|
1115
|
-
this.#report = report;
|
|
1116
|
-
this.#ports = ports;
|
|
1117
|
-
this.logger = logger;
|
|
1118
|
-
}
|
|
1119
|
-
get replacementSafe() {
|
|
1120
|
-
return this.#coordinator?.replacementSafe ?? this.#retiredWithoutCoordinator;
|
|
1121
|
-
}
|
|
1122
|
-
get unresolvedReportedDomainCount() {
|
|
1123
|
-
return this.#reportedFailures.size;
|
|
1124
|
-
}
|
|
1125
|
-
get configuredOwnerCount() {
|
|
1126
|
-
return this.#configuredOwners.size;
|
|
1127
|
-
}
|
|
1128
|
-
get configuredScopeCount() {
|
|
1129
|
-
return this.#coordinator?.configuredScopeCount ?? 0;
|
|
1130
|
-
}
|
|
1131
|
-
hasRegistration(token) {
|
|
1132
|
-
return this.#registrations.has(token);
|
|
1133
|
-
}
|
|
1134
|
-
registrationEndpointSafe(token) {
|
|
1135
|
-
const state = this.#registrations.get(token);
|
|
1136
|
-
return state === undefined || (state.detach?.quiescent === true && state.detach.barrier);
|
|
1137
|
-
}
|
|
1138
|
-
async attach(claim, descriptors, transferred = false, binding = { generation: claim.generation }, transferredReadiness) {
|
|
1139
|
-
this.#requireFresh(descriptors);
|
|
1140
|
-
const registration = await EnvironmentAttachmentAssembly.assembleRegistration(descriptors, (descriptor, tenant, context, endpoints, storageFactory) => this.#runtimeFromSnapshot(descriptor, tenant, context, endpoints, storageFactory));
|
|
1141
|
-
const ownership = new RegistrationOwnership();
|
|
1142
|
-
for (const runtime of registration.runtimes) {
|
|
1143
|
-
this.#addRuntime(runtime);
|
|
1144
|
-
ownership.add(runtime);
|
|
1145
|
-
}
|
|
1146
|
-
this.#deliveryRecords.register(claim.token, ownership.scopes);
|
|
1147
|
-
const readiness = transferredReadiness ?? this.#readiness(registration, ownership, claim.token);
|
|
1148
|
-
if (transferredReadiness !== undefined) {
|
|
1149
|
-
readiness.rebind((descriptor, ready) => this.#prepareReady(descriptor, ready, ownership, claim.token), (scope) => {
|
|
1150
|
-
this.#notify(scope);
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
this.#registrations.set(claim.token, {
|
|
1154
|
-
readiness,
|
|
1155
|
-
startupScopes: registration.scopes,
|
|
1156
|
-
ownership,
|
|
1157
|
-
descriptors,
|
|
1158
|
-
scopeDescriptors: EnvironmentAttachmentAssembly.registrationScopeDescriptors(registration),
|
|
1159
|
-
});
|
|
1160
|
-
if (!transferred) {
|
|
1161
|
-
await this.#transition(registration, readiness);
|
|
1162
|
-
}
|
|
1163
|
-
return await this.#recover(claim, binding, transferred ? registration.scopes : readiness.open(registration.scopes));
|
|
1164
|
-
}
|
|
1165
|
-
prepareTransferredRoute(claim, snapshot, readiness) {
|
|
1166
|
-
const descriptor = snapshot.descriptor;
|
|
1167
|
-
this.#validateFreshDescriptors([descriptor]);
|
|
1168
|
-
const registration = EnvironmentAttachmentAssembly.assembleRegistrationSnapshots([snapshot], (candidate, tenant, context, endpoints, storageFactory) => this.#runtimeFromSnapshot(candidate, tenant, context, endpoints, storageFactory));
|
|
1169
|
-
let state = this.#registrations.get(claim.token);
|
|
1170
|
-
if (state === undefined) {
|
|
1171
|
-
state = {
|
|
1172
|
-
readiness,
|
|
1173
|
-
startupScopes: Object.freeze([]),
|
|
1174
|
-
ownership: new RegistrationOwnership(),
|
|
1175
|
-
descriptors: Object.freeze([]),
|
|
1176
|
-
scopeDescriptors: new Map(),
|
|
1177
|
-
};
|
|
1178
|
-
this.#registrations.set(claim.token, state);
|
|
1179
|
-
}
|
|
1180
|
-
for (const runtime of registration.runtimes) {
|
|
1181
|
-
this.#addRuntime(runtime);
|
|
1182
|
-
state.ownership.add(runtime);
|
|
1183
|
-
}
|
|
1184
|
-
this.#deliveryRecords.register(claim.token, registration.scopes);
|
|
1185
|
-
for (const scope of registration.scopes) {
|
|
1186
|
-
state.scopeDescriptors.set(EnvironmentAttachmentValues.scopeKey(scope), descriptor);
|
|
1187
|
-
}
|
|
1188
|
-
state.startupScopes = EnvironmentAttachmentValues.appendScopes(state.startupScopes, registration.scopes);
|
|
1189
|
-
state.descriptors = Object.freeze([...state.descriptors, descriptor]);
|
|
1190
|
-
const ownership = state.ownership;
|
|
1191
|
-
readiness.rebindDescriptor(descriptor, (candidate, ready) => this.#prepareReady(candidate, ready, ownership, claim.token), (scope) => {
|
|
1192
|
-
this.#notify(scope);
|
|
1193
|
-
});
|
|
1194
|
-
this.#descriptors.add(descriptor);
|
|
1195
|
-
return Promise.resolve(registration.scopes);
|
|
1196
|
-
}
|
|
1197
|
-
async recoverTransferred(token, descriptor, ready) {
|
|
1198
|
-
const state = this.#registrations.get(token);
|
|
1199
|
-
if (state === undefined) {
|
|
1200
|
-
throw new Error("Environment registration is not active.");
|
|
1201
|
-
}
|
|
1202
|
-
const scope = this.#prepareReady(descriptor, ready, state.ownership, token);
|
|
1203
|
-
const domains = [this.#overlapDomain(scope)];
|
|
1204
|
-
const previouslyBlocked = this.#reportedFailures.overlaps(domains);
|
|
1205
|
-
const settlement = await this.#start([scope]);
|
|
1206
|
-
this.#reportedFailures.resolve(this.#resolvedDomains([scope], settlement));
|
|
1207
|
-
const blocked = previouslyBlocked && this.#reportedFailures.overlaps(domains);
|
|
1208
|
-
EnvironmentAttachmentAssembly.throwRejected(this.#deliveryRecords.registrationRecords(token), blocked);
|
|
1209
|
-
}
|
|
1210
|
-
async rollbackRegistration(token) {
|
|
1211
|
-
const state = this.#registrations.get(token);
|
|
1212
|
-
if (state === undefined) {
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
state.readiness.fail();
|
|
1216
|
-
const ownerKeys = state.ownership.ownerKeys;
|
|
1217
|
-
const scopes = state.ownership.scopes;
|
|
1218
|
-
const rollback = (state.rollback ??= { stopped: false, quiescent: false });
|
|
1219
|
-
if (!rollback.stopped) {
|
|
1220
|
-
this.#worker.stopOwners(ownerKeys);
|
|
1221
|
-
rollback.stopped = true;
|
|
1222
|
-
}
|
|
1223
|
-
if (!rollback.quiescent) {
|
|
1224
|
-
await this.#worker.awaitOwnersSettled(ownerKeys);
|
|
1225
|
-
rollback.quiescent = true;
|
|
1226
|
-
}
|
|
1227
|
-
const failures = [];
|
|
1228
|
-
try {
|
|
1229
|
-
await this.#consumeRegistration(token, false);
|
|
1230
|
-
}
|
|
1231
|
-
catch (error) {
|
|
1232
|
-
failures.push(error);
|
|
1233
|
-
}
|
|
1234
|
-
try {
|
|
1235
|
-
await this.#worker.retireOwners(ownerKeys);
|
|
1236
|
-
}
|
|
1237
|
-
catch (error) {
|
|
1238
|
-
failures.push(error);
|
|
1239
|
-
}
|
|
1240
|
-
let reclaimed = false;
|
|
1241
|
-
try {
|
|
1242
|
-
await this.#coordinator?.removeOwners(ownerKeys);
|
|
1243
|
-
reclaimed = true;
|
|
1244
|
-
}
|
|
1245
|
-
catch (error) {
|
|
1246
|
-
failures.push(error);
|
|
1247
|
-
}
|
|
1248
|
-
if (reclaimed) {
|
|
1249
|
-
for (const ownerKey of ownerKeys) {
|
|
1250
|
-
this.#configuredOwners.delete(ownerKey);
|
|
1251
|
-
}
|
|
1252
|
-
this.#forgetScopes(scopes);
|
|
1253
|
-
}
|
|
1254
|
-
this.#registrations.delete(token);
|
|
1255
|
-
EnvironmentAttachmentValues.throwFailures(failures, "Environment registration rollback failed.");
|
|
1256
|
-
}
|
|
1257
|
-
async detachRegistration(token) {
|
|
1258
|
-
const state = this.#registrations.get(token);
|
|
1259
|
-
if (state === undefined) {
|
|
1260
|
-
return;
|
|
1261
|
-
}
|
|
1262
|
-
state.readiness.fail();
|
|
1263
|
-
const ownerKeys = state.ownership.ownerKeys;
|
|
1264
|
-
const scopes = state.ownership.scopes;
|
|
1265
|
-
const detach = (state.detach ??= {
|
|
1266
|
-
stopped: false,
|
|
1267
|
-
quiescent: false,
|
|
1268
|
-
barrier: false,
|
|
1269
|
-
recordsConsumed: false,
|
|
1270
|
-
causes: Object.freeze([]),
|
|
1271
|
-
reportAttempted: false,
|
|
1272
|
-
workerRetirementAttempted: false,
|
|
1273
|
-
coordinatorRemoved: false,
|
|
1274
|
-
});
|
|
1275
|
-
if (!detach.stopped) {
|
|
1276
|
-
this.#worker.stopOwners(ownerKeys);
|
|
1277
|
-
detach.stopped = true;
|
|
1278
|
-
}
|
|
1279
|
-
if (!detach.quiescent) {
|
|
1280
|
-
await this.#worker.awaitOwnersSettled(ownerKeys);
|
|
1281
|
-
detach.quiescent = true;
|
|
1282
|
-
}
|
|
1283
|
-
if (!detach.barrier) {
|
|
1284
|
-
await this.#coordinator?.awaitOwnersBarrier(ownerKeys);
|
|
1285
|
-
detach.barrier = true;
|
|
1286
|
-
}
|
|
1287
|
-
const failures = [];
|
|
1288
|
-
if (!detach.recordsConsumed) {
|
|
1289
|
-
try {
|
|
1290
|
-
detach.causes = this.#deliveryRecords.detach(token);
|
|
1291
|
-
detach.recordsConsumed = true;
|
|
1292
|
-
}
|
|
1293
|
-
catch (error) {
|
|
1294
|
-
failures.push(error);
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
if (detach.recordsConsumed && !detach.reportAttempted) {
|
|
1298
|
-
detach.reportAttempted = true;
|
|
1299
|
-
try {
|
|
1300
|
-
if (detach.causes.length > 0) {
|
|
1301
|
-
await this.#report(detach.causes);
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
catch (error) {
|
|
1305
|
-
failures.push(error);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
if (!detach.workerRetirementAttempted) {
|
|
1309
|
-
detach.workerRetirementAttempted = true;
|
|
1310
|
-
try {
|
|
1311
|
-
await this.#worker.retireOwners(ownerKeys);
|
|
1312
|
-
}
|
|
1313
|
-
catch (error) {
|
|
1314
|
-
failures.push(error);
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
if (!detach.coordinatorRemoved) {
|
|
1318
|
-
try {
|
|
1319
|
-
if (ownerKeys.length > 0) {
|
|
1320
|
-
await this.#coordinator?.removeOwners(ownerKeys);
|
|
1321
|
-
}
|
|
1322
|
-
detach.coordinatorRemoved = true;
|
|
1323
|
-
}
|
|
1324
|
-
catch (error) {
|
|
1325
|
-
failures.push(error);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
if (detach.coordinatorRemoved) {
|
|
1329
|
-
for (const ownerKey of ownerKeys) {
|
|
1330
|
-
this.#configuredOwners.delete(ownerKey);
|
|
1331
|
-
}
|
|
1332
|
-
this.#forgetScopes(scopes);
|
|
1333
|
-
for (const descriptor of state.descriptors) {
|
|
1334
|
-
this.#descriptors.delete(descriptor);
|
|
1335
|
-
this.#runtimes.delete(descriptor);
|
|
1336
|
-
}
|
|
1337
|
-
this.#registrations.delete(token);
|
|
1338
|
-
}
|
|
1339
|
-
EnvironmentAttachmentValues.throwFailures(failures, "Environment registration detach failed.");
|
|
1340
|
-
}
|
|
1341
|
-
async retireRegistration(token) {
|
|
1342
|
-
this.#registrations.get(token)?.readiness.fail();
|
|
1343
|
-
if (this.#coordinator === undefined) {
|
|
1344
|
-
try {
|
|
1345
|
-
await this.#consumeRegistration(token, true);
|
|
1346
|
-
}
|
|
1347
|
-
finally {
|
|
1348
|
-
this.#registrations.delete(token);
|
|
1349
|
-
this.#retiredWithoutCoordinator = true;
|
|
1350
|
-
this.#dropRetiredState();
|
|
1351
|
-
}
|
|
1352
|
-
return;
|
|
1353
|
-
}
|
|
1354
|
-
try {
|
|
1355
|
-
await this.#coordinator.retire(async () => {
|
|
1356
|
-
try {
|
|
1357
|
-
await this.#consumeRegistration(token, true);
|
|
1358
|
-
}
|
|
1359
|
-
finally {
|
|
1360
|
-
this.#registrations.delete(token);
|
|
1361
|
-
}
|
|
1362
|
-
});
|
|
1363
|
-
}
|
|
1364
|
-
finally {
|
|
1365
|
-
if (this.replacementSafe) {
|
|
1366
|
-
this.#dropRetiredState();
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
async retire() {
|
|
1371
|
-
const token = this.#registrations.keys().next().value;
|
|
1372
|
-
if (token === undefined) {
|
|
1373
|
-
this.#retiredWithoutCoordinator = true;
|
|
1374
|
-
return Object.freeze({ status: "succeeded" });
|
|
1375
|
-
}
|
|
1376
|
-
const coordinator = this.#coordinator;
|
|
1377
|
-
try {
|
|
1378
|
-
if (coordinator === undefined) {
|
|
1379
|
-
await this.#consumeRegistration(token, true);
|
|
1380
|
-
this.#retiredWithoutCoordinator = true;
|
|
1381
|
-
}
|
|
1382
|
-
else {
|
|
1383
|
-
await coordinator.retire(() => this.#consumeRegistration(token, true));
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
catch (reason) {
|
|
1387
|
-
return Object.freeze({
|
|
1388
|
-
status: "failed",
|
|
1389
|
-
reason,
|
|
1390
|
-
causes: coordinator?.takeRetirementFailureCauses(reason) ?? Object.freeze([reason]),
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
this.#registrations.clear();
|
|
1394
|
-
this.#dropRetiredState();
|
|
1395
|
-
return Object.freeze({ status: "succeeded" });
|
|
1396
|
-
}
|
|
1397
|
-
captureTransition(capture) {
|
|
1398
|
-
const pending = this.#coordinator?.settlement().pending ?? Object.freeze([]);
|
|
1399
|
-
const retained = this.#deliveryRecords.retainedScopeSnapshot(pending);
|
|
1400
|
-
for (const [token, state] of this.#registrations) {
|
|
1401
|
-
this.#captureScopes(token, state, this.#deliveryRecords.configuredScopes(token), "configured", capture);
|
|
1402
|
-
this.#captureScopes(token, state, state.startupScopes, "startup", capture);
|
|
1403
|
-
this.#captureScopes(token, state, retained.get(token) ?? Object.freeze([]), "retained", capture);
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
closeRegistration(token, onBuffered) {
|
|
1407
|
-
const state = this.#registrations.get(token);
|
|
1408
|
-
if (state === undefined) {
|
|
1409
|
-
throw new Error("Environment registration is not active.");
|
|
1410
|
-
}
|
|
1411
|
-
state.readiness.prepareTransition(onBuffered);
|
|
1412
|
-
return state.readiness;
|
|
1413
|
-
}
|
|
1414
|
-
openRegistration(token) {
|
|
1415
|
-
const state = this.#registrations.get(token);
|
|
1416
|
-
if (state === undefined) {
|
|
1417
|
-
throw new Error("Environment registration is not active.");
|
|
1418
|
-
}
|
|
1419
|
-
return state.readiness.open([]);
|
|
1420
|
-
}
|
|
1421
|
-
async recoverReopened(scopes) {
|
|
1422
|
-
await this.#start(scopes);
|
|
1423
|
-
}
|
|
1424
|
-
async retireFailedRegistration(token) {
|
|
1425
|
-
await this.retireRegistration(token);
|
|
1426
|
-
}
|
|
1427
|
-
#notify(scope) {
|
|
1428
|
-
if (this.#worker.notify === undefined) {
|
|
1429
|
-
this.#coordinator?.notify(scope);
|
|
1430
|
-
return;
|
|
1431
|
-
}
|
|
1432
|
-
this.#worker.notify(scope);
|
|
1433
|
-
}
|
|
1434
|
-
#readiness(registration, ownership, token) {
|
|
1435
|
-
return new RegistrationReadiness(registration.descriptors, (descriptor, ready) => this.#prepareReady(descriptor, ready, ownership, token), (scope) => {
|
|
1436
|
-
this.#notify(scope);
|
|
1437
|
-
});
|
|
1438
|
-
}
|
|
1439
|
-
async #transition(registration, readiness) {
|
|
1440
|
-
await Promise.all(registration.descriptors.map(({ descriptor, scopes }) => descriptor.transition(scopes.map(({ ready }) => ready), (ready) => {
|
|
1441
|
-
readiness.notify(descriptor, ready);
|
|
1442
|
-
}, {
|
|
1443
|
-
allowEmpty: true,
|
|
1444
|
-
...(this.#ports === undefined ? {} : { ports: this.#ports }),
|
|
1445
|
-
})));
|
|
1446
|
-
}
|
|
1447
|
-
async #recover(claim, binding, startup) {
|
|
1448
|
-
const domains = startup.map((scope) => this.#overlapDomain(scope));
|
|
1449
|
-
const previouslyBlocked = this.#reportedFailures.overlaps(domains);
|
|
1450
|
-
const settlement = await this.#start(startup);
|
|
1451
|
-
this.#reportedFailures.resolve(this.#resolvedDomains(startup, settlement));
|
|
1452
|
-
const blocked = previouslyBlocked && this.#reportedFailures.overlaps(domains);
|
|
1453
|
-
const startupRecords = this.#deliveryRecords.registrationRecords(claim.token);
|
|
1454
|
-
EnvironmentAttachmentAssembly.throwRejected(startupRecords, blocked);
|
|
1455
|
-
return EnvironmentAttachmentValues.handle(claim, binding, settlement, () => startupRecords);
|
|
1456
|
-
}
|
|
1457
|
-
async #consumeRegistration(token, consumeGeneration) {
|
|
1458
|
-
const state = this.#registrations.get(token);
|
|
1459
|
-
if (state === undefined) {
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
const causes = consumeGeneration
|
|
1463
|
-
? this.#deliveryRecords.retire()
|
|
1464
|
-
: this.#deliveryRecords.rollback(token);
|
|
1465
|
-
let reportFailure = { status: "none" };
|
|
1466
|
-
try {
|
|
1467
|
-
if (causes.length > 0) {
|
|
1468
|
-
await this.#report(causes);
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
catch (error) {
|
|
1472
|
-
reportFailure = { status: "retained", reason: error };
|
|
1473
|
-
}
|
|
1474
|
-
if (!consumeGeneration) {
|
|
1475
|
-
const unresolved = new Set(this.#deliveryRecords
|
|
1476
|
-
.records()
|
|
1477
|
-
.filter(({ hasCause, reportedSinceResolution }) => hasCause && reportedSinceResolution)
|
|
1478
|
-
.flatMap(({ units: recordUnits }) => recordUnits));
|
|
1479
|
-
this.#reportedFailures.record(state.startupScopes.flatMap((scope) => unresolved.has(EnvironmentAttachmentValues.scopeKey(scope))
|
|
1480
|
-
? [this.#overlapDomain(scope)]
|
|
1481
|
-
: []));
|
|
1482
|
-
}
|
|
1483
|
-
if (reportFailure.status === "retained") {
|
|
1484
|
-
throw reportFailure.reason;
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
async #start(startup) {
|
|
1488
|
-
return startup.length === 0 || this.#coordinator === undefined
|
|
1489
|
-
? EnvironmentAttachmentValues.emptySettlement()
|
|
1490
|
-
: await this.#coordinator.start(startup);
|
|
1491
|
-
}
|
|
1492
|
-
#prepareReady(descriptor, ready, ownership, token) {
|
|
1493
|
-
const tenant = ready.tenantId === undefined
|
|
1494
|
-
? Object.freeze({})
|
|
1495
|
-
: Object.freeze({ tenantId: ready.tenantId });
|
|
1496
|
-
const runtime = this.#runtime(descriptor, tenant);
|
|
1497
|
-
this.#addRuntime(runtime);
|
|
1498
|
-
ownership.add(runtime);
|
|
1499
|
-
this.#deliveryRecords.register(token, runtime.scopes);
|
|
1500
|
-
const state = this.#registrations.get(token);
|
|
1501
|
-
for (const configured of runtime.scopes) {
|
|
1502
|
-
state?.scopeDescriptors.set(EnvironmentAttachmentValues.scopeKey(configured), descriptor);
|
|
1503
|
-
}
|
|
1504
|
-
const scope = runtime.scopes.find(({ ready: candidate }) => EnvironmentAttachmentValues.readyKey(candidate) ===
|
|
1505
|
-
EnvironmentAttachmentValues.readyKey(ready));
|
|
1506
|
-
if (scope === undefined) {
|
|
1507
|
-
throw new Error("Environment readiness is outside the descriptor endpoint domain.");
|
|
1508
|
-
}
|
|
1509
|
-
return scope;
|
|
1510
|
-
}
|
|
1511
|
-
#captureScopes(token, state, scopes, source, capture) {
|
|
1512
|
-
for (const scope of scopes) {
|
|
1513
|
-
const descriptor = state.scopeDescriptors.get(EnvironmentAttachmentValues.scopeKey(scope));
|
|
1514
|
-
if (descriptor === undefined) {
|
|
1515
|
-
throw new Error("Environment transition scope has no descriptor route.");
|
|
1516
|
-
}
|
|
1517
|
-
capture.capture(token, descriptor, [scope], source);
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
#addRuntime(runtime) {
|
|
1521
|
-
if (this.#configuredOwners.has(runtime.owner.key)) {
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
|
-
this.#worker.add(runtime);
|
|
1525
|
-
this.#configuredOwners.add(runtime.owner.key);
|
|
1526
|
-
if (this.#coordinator === undefined) {
|
|
1527
|
-
this.#coordinator = new DeliveryRunCoordinator({
|
|
1528
|
-
scopes: runtime.scopes,
|
|
1529
|
-
worker: this.#worker,
|
|
1530
|
-
onSettlement: (settlement) => {
|
|
1531
|
-
this.#deliveryRecords.observe(settlement);
|
|
1532
|
-
},
|
|
1533
|
-
});
|
|
1534
|
-
}
|
|
1535
|
-
else {
|
|
1536
|
-
this.#coordinator.configure(runtime.scopes);
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
#runtime(descriptor, tenant) {
|
|
1540
|
-
const existing = this.#existingRuntime(descriptor, tenant);
|
|
1541
|
-
if (existing !== undefined) {
|
|
1542
|
-
return existing;
|
|
1543
|
-
}
|
|
1544
|
-
return this.#runtimeFromSnapshot(descriptor, tenant, descriptor.storageContext(tenant), descriptor.endpoints(), descriptor.storageFactory);
|
|
1545
|
-
}
|
|
1546
|
-
#runtimeFromSnapshot(descriptor, tenant, context, endpoints, storageFactory) {
|
|
1547
|
-
const existing = this.#existingRuntime(descriptor, tenant);
|
|
1548
|
-
if (existing !== undefined) {
|
|
1549
|
-
return existing;
|
|
1550
|
-
}
|
|
1551
|
-
const tenantKey = EnvironmentAttachmentValues.tenantKey(tenant);
|
|
1552
|
-
this.#nextOwner += 1;
|
|
1553
|
-
const owner = Object.freeze({ key: `environment-owner-${this.#nextOwner.toString()}` });
|
|
1554
|
-
const runtime = Object.freeze({
|
|
1555
|
-
owner,
|
|
1556
|
-
descriptor,
|
|
1557
|
-
storageFactory,
|
|
1558
|
-
tenant,
|
|
1559
|
-
context,
|
|
1560
|
-
...(this.logger === undefined ? {} : { logger: this.logger }),
|
|
1561
|
-
scopes: Object.freeze(endpoints.map((endpoint) => EnvironmentAttachmentValues.readyScope(owner, endpoint, tenant))),
|
|
1562
|
-
});
|
|
1563
|
-
for (const scope of runtime.scopes) {
|
|
1564
|
-
this.#overlapDomains.set(EnvironmentAttachmentValues.scopeKey(scope), this.#stableDomain(storageFactory, context, scope.ready));
|
|
1565
|
-
}
|
|
1566
|
-
const runtimes = this.#runtimes.get(descriptor) ?? new Map();
|
|
1567
|
-
runtimes.set(tenantKey, runtime);
|
|
1568
|
-
this.#runtimes.set(descriptor, runtimes);
|
|
1569
|
-
return runtime;
|
|
1570
|
-
}
|
|
1571
|
-
#existingRuntime(descriptor, tenant) {
|
|
1572
|
-
return this.#runtimes.get(descriptor)?.get(EnvironmentAttachmentValues.tenantKey(tenant));
|
|
1573
|
-
}
|
|
1574
|
-
#stableDomain(factory, context, ready) {
|
|
1575
|
-
let factoryId = this.#factoryIds.get(factory);
|
|
1576
|
-
if (factoryId === undefined) {
|
|
1577
|
-
this.#nextFactory += 1;
|
|
1578
|
-
factoryId = this.#nextFactory;
|
|
1579
|
-
this.#factoryIds.set(factory, factoryId);
|
|
1580
|
-
}
|
|
1581
|
-
return JSON.stringify([
|
|
1582
|
-
factoryId,
|
|
1583
|
-
context.name,
|
|
1584
|
-
context.multitenant,
|
|
1585
|
-
context.tenantId ?? null,
|
|
1586
|
-
EnvironmentAttachmentValues.readyKey(ready),
|
|
1587
|
-
]);
|
|
1588
|
-
}
|
|
1589
|
-
#overlapDomain(scope) {
|
|
1590
|
-
const domain = this.#overlapDomains.get(EnvironmentAttachmentValues.scopeKey(scope));
|
|
1591
|
-
if (domain === undefined) {
|
|
1592
|
-
throw new Error("Environment overlap domain is not configured.");
|
|
1593
|
-
}
|
|
1594
|
-
return domain;
|
|
1595
|
-
}
|
|
1596
|
-
#resolvedDomains(startup, settlement) {
|
|
1597
|
-
const current = new Set(startup.map((scope) => EnvironmentAttachmentValues.scopeKey(scope)));
|
|
1598
|
-
return settlement.scopes.flatMap(({ scope, disposition }) => current.has(EnvironmentAttachmentValues.scopeKey(scope)) && disposition !== "REJECTED"
|
|
1599
|
-
? [this.#overlapDomain(scope)]
|
|
1600
|
-
: []);
|
|
1601
|
-
}
|
|
1602
|
-
#forgetScopes(scopes) {
|
|
1603
|
-
for (const scope of scopes) {
|
|
1604
|
-
this.#overlapDomains.delete(EnvironmentAttachmentValues.scopeKey(scope));
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
#dropRetiredState() {
|
|
1608
|
-
this.#reportedFailures.clear();
|
|
1609
|
-
this.#overlapDomains.clear();
|
|
1610
|
-
this.#factoryIds = new WeakMap();
|
|
1611
|
-
this.#nextFactory = 0;
|
|
1612
|
-
}
|
|
1613
|
-
#requireFresh(descriptors) {
|
|
1614
|
-
for (const descriptor of this.#validateFreshDescriptors(descriptors)) {
|
|
1615
|
-
this.#descriptors.add(descriptor);
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
#validateFreshDescriptors(descriptors) {
|
|
1619
|
-
const unique = new Set();
|
|
1620
|
-
for (const descriptor of descriptors) {
|
|
1621
|
-
if (unique.has(descriptor)) {
|
|
1622
|
-
throw new Error("Attachment requires unique context delivery descriptors.");
|
|
1623
|
-
}
|
|
1624
|
-
if (this.#descriptors.has(descriptor)) {
|
|
1625
|
-
throw new Error("Context delivery descriptor is already attached.");
|
|
1626
|
-
}
|
|
1627
|
-
unique.add(descriptor);
|
|
1628
|
-
}
|
|
1629
|
-
return unique;
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
/**
|
|
1633
|
-
* Buffers descriptor readiness while one attachment transitions between generations.
|
|
1634
|
-
*
|
|
1635
|
-
* @internal
|
|
1636
|
-
*/
|
|
1637
|
-
export class RegistrationReadiness {
|
|
1638
|
-
#descriptors;
|
|
1639
|
-
#canonical = new WeakMap();
|
|
1640
|
-
#destinations = new WeakMap();
|
|
1641
|
-
#buffered = new Map();
|
|
1642
|
-
#onTransition;
|
|
1643
|
-
#mode = "waiting";
|
|
1644
|
-
#invalid = false;
|
|
1645
|
-
/**
|
|
1646
|
-
* Creates readiness buffering for configured descriptors.
|
|
1647
|
-
*
|
|
1648
|
-
* @param descriptors Supplies descriptors and their initial scopes.
|
|
1649
|
-
* @param onPrepare Converts a readiness notification into a scope.
|
|
1650
|
-
* @param onNotify Delivers an opened scope to its generation.
|
|
1651
|
-
*/
|
|
1652
|
-
constructor(descriptors, onPrepare, onNotify) {
|
|
1653
|
-
this.#descriptors = Object.freeze(descriptors.map(({ descriptor }) => descriptor));
|
|
1654
|
-
for (const { descriptor, scopes } of descriptors) {
|
|
1655
|
-
this.#canonical.set(descriptor, new Map(scopes.map((scope) => [EnvironmentAttachmentValues.readyKey(scope.ready), scope])));
|
|
1656
|
-
this.#destinations.set(descriptor, { onPrepare, onNotify });
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
/**
|
|
1660
|
-
* Records or forwards one endpoint readiness notification.
|
|
1661
|
-
*
|
|
1662
|
-
* @param descriptor Identifies the reporting descriptor.
|
|
1663
|
-
* @param ready Supplies the ready endpoint.
|
|
1664
|
-
*/
|
|
1665
|
-
notify(descriptor, ready) {
|
|
1666
|
-
if (this.#mode === "failed") {
|
|
1667
|
-
return;
|
|
1668
|
-
}
|
|
1669
|
-
if (this.#mode === "open") {
|
|
1670
|
-
try {
|
|
1671
|
-
const destination = this.#destinations.get(descriptor);
|
|
1672
|
-
if (destination === undefined) {
|
|
1673
|
-
throw new Error("Registration readiness descriptor is not configured.");
|
|
1674
|
-
}
|
|
1675
|
-
const scope = destination.onPrepare(descriptor, ready);
|
|
1676
|
-
destination.onNotify(scope);
|
|
1677
|
-
}
|
|
1678
|
-
catch {
|
|
1679
|
-
this.#mode = "failed";
|
|
1680
|
-
}
|
|
1681
|
-
return;
|
|
1682
|
-
}
|
|
1683
|
-
if (this.#onTransition !== undefined) {
|
|
1684
|
-
try {
|
|
1685
|
-
const snapshot = EnvironmentAttachmentValues.cloneReady(ready);
|
|
1686
|
-
this.#onTransition(descriptor, snapshot);
|
|
1687
|
-
this.#buffer(descriptor, snapshot);
|
|
1688
|
-
}
|
|
1689
|
-
catch {
|
|
1690
|
-
this.#invalid = true;
|
|
1691
|
-
}
|
|
1692
|
-
return;
|
|
1693
|
-
}
|
|
1694
|
-
const scope = this.#canonical.get(descriptor)?.get(EnvironmentAttachmentValues.readyKey(ready));
|
|
1695
|
-
if (scope === undefined) {
|
|
1696
|
-
this.#invalid = true;
|
|
1697
|
-
return;
|
|
1698
|
-
}
|
|
1699
|
-
this.#buffer(descriptor, EnvironmentAttachmentValues.cloneReady(scope.ready));
|
|
1700
|
-
}
|
|
1701
|
-
/**
|
|
1702
|
-
* Clears readiness and discards buffered notifications.
|
|
1703
|
-
*/
|
|
1704
|
-
fail() {
|
|
1705
|
-
this.#mode = "failed";
|
|
1706
|
-
this.#buffered.clear();
|
|
1707
|
-
this.#onTransition = undefined;
|
|
1708
|
-
}
|
|
1709
|
-
/**
|
|
1710
|
-
* Prepares notification buffering for a generation transition.
|
|
1711
|
-
*
|
|
1712
|
-
* @param onBuffered Receives notifications buffered during the transition.
|
|
1713
|
-
*/
|
|
1714
|
-
prepareTransition(onBuffered) {
|
|
1715
|
-
if (this.#mode !== "open") {
|
|
1716
|
-
throw new Error("Registration readiness is not open.");
|
|
1717
|
-
}
|
|
1718
|
-
this.#onTransition = onBuffered;
|
|
1719
|
-
this.#mode = "waiting";
|
|
1720
|
-
}
|
|
1721
|
-
/**
|
|
1722
|
-
* Updates every descriptor for a replacement generation.
|
|
1723
|
-
*
|
|
1724
|
-
* @param onPrepare Converts readiness into replacement scopes.
|
|
1725
|
-
* @param onNotify Delivers opened replacement scopes.
|
|
1726
|
-
*/
|
|
1727
|
-
rebind(onPrepare, onNotify) {
|
|
1728
|
-
for (const descriptor of this.#descriptors) {
|
|
1729
|
-
this.rebindDescriptor(descriptor, onPrepare, onNotify);
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
/**
|
|
1733
|
-
* Updates one descriptor for a replacement generation.
|
|
1734
|
-
*
|
|
1735
|
-
* @param descriptor Identifies the descriptor to rebind.
|
|
1736
|
-
* @param onPrepare Converts readiness into replacement scopes.
|
|
1737
|
-
* @param onNotify Delivers opened replacement scopes.
|
|
1738
|
-
*/
|
|
1739
|
-
rebindDescriptor(descriptor, onPrepare, onNotify) {
|
|
1740
|
-
if (this.#mode !== "waiting") {
|
|
1741
|
-
throw new Error("Registration readiness is not prepared for transition.");
|
|
1742
|
-
}
|
|
1743
|
-
this.#destinations.set(descriptor, { onPrepare, onNotify });
|
|
1744
|
-
}
|
|
1745
|
-
/**
|
|
1746
|
-
* Opens readiness and returns initial plus buffered scopes.
|
|
1747
|
-
*
|
|
1748
|
-
* @param startup Supplies scopes recovered during startup.
|
|
1749
|
-
* @returns Immutable scopes to run or notify.
|
|
1750
|
-
*/
|
|
1751
|
-
open(startup) {
|
|
1752
|
-
if (this.#mode !== "waiting") {
|
|
1753
|
-
throw new Error("Registration readiness can only open once.");
|
|
1754
|
-
}
|
|
1755
|
-
if (this.#invalid) {
|
|
1756
|
-
this.#mode = "failed";
|
|
1757
|
-
this.#buffered.clear();
|
|
1758
|
-
throw new Error("Registration readiness received an unconfigured scope.");
|
|
1759
|
-
}
|
|
1760
|
-
const scopes = new Map();
|
|
1761
|
-
for (const scope of startup) {
|
|
1762
|
-
scopes.set(EnvironmentAttachmentValues.scopeKey(scope), scope);
|
|
1763
|
-
}
|
|
1764
|
-
for (const [descriptor, readies] of this.#buffered) {
|
|
1765
|
-
const destination = this.#destinations.get(descriptor);
|
|
1766
|
-
if (destination === undefined) {
|
|
1767
|
-
throw new Error("Registration readiness descriptor is not configured.");
|
|
1768
|
-
}
|
|
1769
|
-
for (const ready of readies.values()) {
|
|
1770
|
-
const scope = destination.onPrepare(descriptor, ready);
|
|
1771
|
-
scopes.set(EnvironmentAttachmentValues.scopeKey(scope), scope);
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
this.#buffered.clear();
|
|
1775
|
-
this.#onTransition = undefined;
|
|
1776
|
-
this.#mode = "open";
|
|
1777
|
-
return Object.freeze([...scopes.values()]);
|
|
1778
|
-
}
|
|
1779
|
-
#buffer(descriptor, ready) {
|
|
1780
|
-
const readies = this.#buffered.get(descriptor) ?? new Map();
|
|
1781
|
-
readies.set(EnvironmentAttachmentValues.readyKey(ready), ready);
|
|
1782
|
-
this.#buffered.set(descriptor, readies);
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
/**
|
|
1786
|
-
* Groups immutable registration-assembly operations.
|
|
1787
|
-
* @internal
|
|
1788
|
-
*/
|
|
1789
|
-
const EnvironmentAttachmentAssembly = Object.freeze({
|
|
1790
|
-
environmentClosedError() {
|
|
1791
|
-
return new Error("ServerEnvironment is closed.");
|
|
1792
|
-
},
|
|
1793
|
-
async assembleRegistration(descriptors, runtimeFor) {
|
|
1794
|
-
const snapshots = [];
|
|
1795
|
-
for (const descriptor of descriptors) {
|
|
1796
|
-
snapshots.push(await EnvironmentAttachmentAssembly.snapshotDescriptor(descriptor));
|
|
1797
|
-
}
|
|
1798
|
-
return EnvironmentAttachmentAssembly.assembleRegistrationSnapshots(snapshots, runtimeFor);
|
|
1799
|
-
},
|
|
1800
|
-
async snapshotDescriptor(descriptor) {
|
|
1801
|
-
const storageFactory = descriptor.storageFactory;
|
|
1802
|
-
const tenants = await descriptor.startupScopes();
|
|
1803
|
-
const endpoints = Object.freeze(descriptor.endpoints().map((endpoint) => Object.freeze({
|
|
1804
|
-
label: endpoint.label,
|
|
1805
|
-
targetTypeUrl: endpoint.targetTypeUrl,
|
|
1806
|
-
shard: new ShardIndex(endpoint.shard.index, endpoint.shard.ofTotal),
|
|
1807
|
-
})));
|
|
1808
|
-
const startup = tenants.map((tenant) => {
|
|
1809
|
-
const capturedTenant = Object.freeze({
|
|
1810
|
-
...(tenant.tenantId === undefined
|
|
1811
|
-
? {}
|
|
1812
|
-
: { tenantId: clone(TenantIdSchema, tenant.tenantId) }),
|
|
1813
|
-
});
|
|
1814
|
-
const context = descriptor.storageContext(capturedTenant);
|
|
1815
|
-
return Object.freeze({
|
|
1816
|
-
tenant: capturedTenant,
|
|
1817
|
-
context: context.multitenant
|
|
1818
|
-
? Object.freeze({
|
|
1819
|
-
name: context.name,
|
|
1820
|
-
multitenant: true,
|
|
1821
|
-
tenantId: clone(TenantIdSchema, context.tenantId),
|
|
1822
|
-
})
|
|
1823
|
-
: Object.freeze({ name: context.name, multitenant: false }),
|
|
1824
|
-
});
|
|
1825
|
-
});
|
|
1826
|
-
return Object.freeze({
|
|
1827
|
-
descriptor,
|
|
1828
|
-
storageFactory,
|
|
1829
|
-
startup: Object.freeze(startup),
|
|
1830
|
-
endpoints,
|
|
1831
|
-
});
|
|
1832
|
-
},
|
|
1833
|
-
assembleRegistrationSnapshots(snapshots, runtimeFor) {
|
|
1834
|
-
const scopes = new Map();
|
|
1835
|
-
const runtimes = [];
|
|
1836
|
-
const assembled = [];
|
|
1837
|
-
for (const { descriptor, storageFactory, startup, endpoints } of snapshots) {
|
|
1838
|
-
const descriptorScopes = [];
|
|
1839
|
-
for (const { tenant, context } of startup) {
|
|
1840
|
-
const runtime = runtimeFor(descriptor, tenant, context, endpoints, storageFactory);
|
|
1841
|
-
const runtimeScopes = runtime.scopes;
|
|
1842
|
-
if (runtimeScopes.length > 0) {
|
|
1843
|
-
runtimes.push(runtime);
|
|
1844
|
-
}
|
|
1845
|
-
for (const scope of runtimeScopes) {
|
|
1846
|
-
scopes.set(EnvironmentAttachmentValues.scopeKey(scope), scope);
|
|
1847
|
-
descriptorScopes.push(scope);
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
if (endpoints.length > 0) {
|
|
1851
|
-
assembled.push({ descriptor, scopes: Object.freeze(descriptorScopes) });
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
return {
|
|
1855
|
-
scopes: Object.freeze([...scopes.values()]),
|
|
1856
|
-
runtimes: Object.freeze(runtimes),
|
|
1857
|
-
descriptors: Object.freeze(assembled),
|
|
1858
|
-
};
|
|
1859
|
-
},
|
|
1860
|
-
registrationScopeDescriptors(registration) {
|
|
1861
|
-
const descriptors = new Map();
|
|
1862
|
-
for (const { descriptor, scopes } of registration.descriptors) {
|
|
1863
|
-
for (const scope of scopes) {
|
|
1864
|
-
descriptors.set(EnvironmentAttachmentValues.scopeKey(scope), descriptor);
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
return descriptors;
|
|
1868
|
-
},
|
|
1869
|
-
/**
|
|
1870
|
-
* Converts a finite startup settlement into registration-scoped parked evidence.
|
|
1871
|
-
*
|
|
1872
|
-
* @param token Identifies the registration.
|
|
1873
|
-
* @param scopes Supplies configured startup scopes.
|
|
1874
|
-
* @param settlement Supplies the finite recovery settlement.
|
|
1875
|
-
* @returns Parked-obligation evidence for the registration.
|
|
1876
|
-
* @internal
|
|
1877
|
-
*/
|
|
1878
|
-
startupObligations(token, scopes, settlement) {
|
|
1879
|
-
const units = scopes.map((scope) => EnvironmentAttachmentValues.scopeKey(scope));
|
|
1880
|
-
const obligations = new ParkedDeliveryObligations({
|
|
1881
|
-
registrations: [{ token, obligations: [{ key: "startup", units }] }],
|
|
1882
|
-
generation: [{ key: "generation", units }],
|
|
1883
|
-
});
|
|
1884
|
-
const configured = new Set(units);
|
|
1885
|
-
for (const result of settlement.scopes) {
|
|
1886
|
-
const unit = EnvironmentAttachmentValues.scopeKey(result.scope);
|
|
1887
|
-
if (!configured.has(unit)) {
|
|
1888
|
-
continue;
|
|
1889
|
-
}
|
|
1890
|
-
const owner = { kind: "registration", token };
|
|
1891
|
-
if (result.disposition === "REJECTED") {
|
|
1892
|
-
obligations.park(owner, "startup", [unit], result.cause);
|
|
1893
|
-
}
|
|
1894
|
-
else if (result.disposition === "PARKED") {
|
|
1895
|
-
obligations.parkFulfilledFailed(owner, "startup", [unit]);
|
|
1896
|
-
}
|
|
1897
|
-
else {
|
|
1898
|
-
obligations.fulfilled(owner, "startup", [unit]);
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
return obligations;
|
|
1902
|
-
},
|
|
1903
|
-
throwRejected(records, blocked) {
|
|
1904
|
-
if (blocked) {
|
|
1905
|
-
throw new Error("Startup recovery is blocked by an unresolved shared delivery obligation.");
|
|
1906
|
-
}
|
|
1907
|
-
const causes = records.flatMap((record) => (record.hasCause ? [record.cause] : []));
|
|
1908
|
-
if (causes.length === 1) {
|
|
1909
|
-
throw causes[0];
|
|
1910
|
-
}
|
|
1911
|
-
if (causes.length > 1) {
|
|
1912
|
-
throw new AggregateError(causes, "Environment attachment startup recovery failed.");
|
|
1913
|
-
}
|
|
1914
|
-
},
|
|
1915
|
-
});
|
|
1916
|
-
/**
|
|
1917
|
-
* Exposes owner-bound registration-assembly test operations.
|
|
1918
|
-
* @internal
|
|
1919
|
-
*/
|
|
1920
|
-
export const EnvironmentAttachmentAccess = Object.freeze({
|
|
1921
|
-
startupObligations(token, scopes, settlement) {
|
|
1922
|
-
return EnvironmentAttachmentAssembly.startupObligations(token, scopes, settlement);
|
|
1923
|
-
},
|
|
1924
|
-
});
|
|
1925
|
-
class ReportedFailures {
|
|
1926
|
-
#domains = new Set();
|
|
1927
|
-
get size() {
|
|
1928
|
-
return this.#domains.size;
|
|
1929
|
-
}
|
|
1930
|
-
record(domains) {
|
|
1931
|
-
for (const domain of domains) {
|
|
1932
|
-
this.#domains.add(domain);
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
overlaps(domains) {
|
|
1936
|
-
return domains.some((domain) => this.#domains.has(domain));
|
|
1937
|
-
}
|
|
1938
|
-
resolve(domains) {
|
|
1939
|
-
for (const domain of domains) {
|
|
1940
|
-
this.#domains.delete(domain);
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
clear() {
|
|
1944
|
-
this.#domains.clear();
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
const currentAggregationFailure = Symbol("currentAggregationFailure");
|
|
1948
|
-
/**
|
|
1949
|
-
*
|
|
1950
|
-
* @internal Groups private attachment-state, scope, and failure operations.
|
|
1951
|
-
*/
|
|
1952
|
-
const EnvironmentAttachmentValues = Object.freeze({
|
|
1953
|
-
tenantKey(tenant) {
|
|
1954
|
-
return tenant.tenantId === undefined
|
|
1955
|
-
? "\u0000"
|
|
1956
|
-
: String(TenantBoundary.from(tenant.tenantId).key);
|
|
1957
|
-
},
|
|
1958
|
-
explicitRetryError() {
|
|
1959
|
-
return new Error("Environment generation rollback requires an explicit retry.");
|
|
1960
|
-
},
|
|
1961
|
-
detachRetryRequiredError() {
|
|
1962
|
-
return new Error("Environment generation detach requires an explicit retry.");
|
|
1963
|
-
},
|
|
1964
|
-
deliveryStopRetryError() {
|
|
1965
|
-
return new Error("Environment delivery stop requires an explicit retry.");
|
|
1966
|
-
},
|
|
1967
|
-
failedStartError(startError, rollbackError) {
|
|
1968
|
-
const rollbackErrors = rollbackError instanceof AggregateError
|
|
1969
|
-
? rollbackError.errors
|
|
1970
|
-
: [rollbackError];
|
|
1971
|
-
return new AggregateError([
|
|
1972
|
-
EnvironmentAttachmentValues.asError(startError),
|
|
1973
|
-
...rollbackErrors.map((error) => EnvironmentAttachmentValues.asError(error)),
|
|
1974
|
-
], "Environment attachment failed and rollback also failed.");
|
|
1975
|
-
},
|
|
1976
|
-
throwFailures(failures, message) {
|
|
1977
|
-
if (failures.length === 1) {
|
|
1978
|
-
throw failures[0];
|
|
1979
|
-
}
|
|
1980
|
-
if (failures.length > 1) {
|
|
1981
|
-
throw new AggregateError(failures, message);
|
|
1982
|
-
}
|
|
1983
|
-
},
|
|
1984
|
-
throwCurrentAggregation(failures) {
|
|
1985
|
-
if (failures.length === 1) {
|
|
1986
|
-
throw failures[0];
|
|
1987
|
-
}
|
|
1988
|
-
if (failures.length > 1) {
|
|
1989
|
-
// Exact arbitrary failure identity is part of the internal retry contract.
|
|
1990
|
-
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
1991
|
-
throw Object.freeze({
|
|
1992
|
-
[currentAggregationFailure]: true,
|
|
1993
|
-
causes: Object.freeze([...failures]),
|
|
1994
|
-
});
|
|
1995
|
-
}
|
|
1996
|
-
throw new Error("Environment transition aggregation requires at least one failure.");
|
|
1997
|
-
},
|
|
1998
|
-
currentAggregationCauses(error) {
|
|
1999
|
-
if (typeof error === "object" &&
|
|
2000
|
-
error !== null &&
|
|
2001
|
-
currentAggregationFailure in error &&
|
|
2002
|
-
error[currentAggregationFailure] === true) {
|
|
2003
|
-
return error.causes;
|
|
2004
|
-
}
|
|
2005
|
-
return Object.freeze([error]);
|
|
2006
|
-
},
|
|
2007
|
-
handle(claim, binding, startup, records) {
|
|
2008
|
-
return new AttachedEnvironmentRegistration(claim, binding, startup, records);
|
|
2009
|
-
},
|
|
2010
|
-
readyScope(owner, endpoint, tenant) {
|
|
2011
|
-
return Object.freeze({
|
|
2012
|
-
owner,
|
|
2013
|
-
ready: Object.freeze({
|
|
2014
|
-
...(tenant.tenantId === undefined ? {} : { tenantId: tenant.tenantId }),
|
|
2015
|
-
label: endpoint.label,
|
|
2016
|
-
targetTypeUrl: endpoint.targetTypeUrl,
|
|
2017
|
-
shard: new ShardIndex(endpoint.shard.index, endpoint.shard.ofTotal),
|
|
2018
|
-
}),
|
|
2019
|
-
});
|
|
2020
|
-
},
|
|
2021
|
-
appendScopes(existing, added) {
|
|
2022
|
-
const scopes = new Map(existing.map((scope) => [EnvironmentAttachmentValues.scopeKey(scope), scope]));
|
|
2023
|
-
for (const scope of added) {
|
|
2024
|
-
scopes.set(EnvironmentAttachmentValues.scopeKey(scope), scope);
|
|
2025
|
-
}
|
|
2026
|
-
return Object.freeze([...scopes.values()]);
|
|
2027
|
-
},
|
|
2028
|
-
scopeKey(scope) {
|
|
2029
|
-
return JSON.stringify([
|
|
2030
|
-
scope.owner.key,
|
|
2031
|
-
scope.ready.tenantId ?? null,
|
|
2032
|
-
scope.ready.label,
|
|
2033
|
-
scope.ready.targetTypeUrl,
|
|
2034
|
-
scope.ready.shard.index,
|
|
2035
|
-
scope.ready.shard.ofTotal,
|
|
2036
|
-
]);
|
|
2037
|
-
},
|
|
2038
|
-
readyKey(scope) {
|
|
2039
|
-
return JSON.stringify([
|
|
2040
|
-
scope.tenantId ?? null,
|
|
2041
|
-
scope.label,
|
|
2042
|
-
scope.targetTypeUrl,
|
|
2043
|
-
scope.shard.index,
|
|
2044
|
-
scope.shard.ofTotal,
|
|
2045
|
-
]);
|
|
2046
|
-
},
|
|
2047
|
-
cloneReady(ready) {
|
|
2048
|
-
return Object.freeze({
|
|
2049
|
-
...(ready.tenantId === undefined ? {} : { tenantId: ready.tenantId }),
|
|
2050
|
-
label: ready.label,
|
|
2051
|
-
targetTypeUrl: ready.targetTypeUrl,
|
|
2052
|
-
shard: new ShardIndex(ready.shard.index, ready.shard.ofTotal),
|
|
2053
|
-
});
|
|
2054
|
-
},
|
|
2055
|
-
emptySettlement() {
|
|
2056
|
-
return Object.freeze({ scopes: Object.freeze([]), pending: Object.freeze([]) });
|
|
2057
|
-
},
|
|
2058
|
-
asError(value) {
|
|
2059
|
-
return value instanceof Error ? value : new Error(String(value));
|
|
2060
|
-
},
|
|
2061
|
-
});
|
|
2062
|
-
//# sourceMappingURL=environment-attachment.js.map
|