@plurnk/plurnk-service 3.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.defaults +3 -3
- package/INSTALL.md +6 -0
- package/README.md +2 -2
- package/SPEC.md +489 -186
- package/dist/build-info.json +1 -1
- package/dist/completions/_plurnk-service +1 -1
- package/dist/completions/plurnk-service.bash +1 -1
- package/dist/completions/plurnk-service.fish +2 -0
- package/dist/content/read-projector.js +1 -1
- package/dist/content/read-projector.js.map +1 -1
- package/dist/core/AdministrativeLoop.d.ts +1 -1
- package/dist/core/AdministrativeLoop.d.ts.map +1 -1
- package/dist/core/AdministrativeLoop.js +4 -1
- package/dist/core/AdministrativeLoop.js.map +1 -1
- package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
- package/dist/core/AdmittedTurnExecutor.js +8 -0
- package/dist/core/AdmittedTurnExecutor.js.map +1 -1
- package/dist/core/BareBatchRunner.js +2 -1
- package/dist/core/BareBatchRunner.js.map +1 -1
- package/dist/core/BudgetReadout.d.ts +1 -3
- package/dist/core/BudgetReadout.d.ts.map +1 -1
- package/dist/core/BudgetReadout.js +43 -46
- package/dist/core/BudgetReadout.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts +10 -0
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +10 -0
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +14 -2
- package/dist/core/ClientInteractions.d.ts.map +1 -1
- package/dist/core/ClientInteractions.js +2 -1
- package/dist/core/ClientInteractions.js.map +1 -1
- package/dist/core/CoreSchemeServices.d.ts.map +1 -1
- package/dist/core/CoreSchemeServices.js +6 -0
- package/dist/core/CoreSchemeServices.js.map +1 -1
- package/dist/core/DataStatementRunner.js +4 -6
- package/dist/core/DataStatementRunner.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +3 -7
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +53 -73
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Dispatcher.sql +2 -2
- package/dist/core/EditMutations.js +1 -0
- package/dist/core/EditMutations.js.map +1 -1
- package/dist/core/Engine.d.ts +8 -14
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +22 -24
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +6 -6
- package/dist/core/ExecutorRegistry.d.ts +0 -5
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +10 -18
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/FileAddress.js +1 -1
- package/dist/core/FileAddress.js.map +1 -1
- package/dist/core/FilePublication.js +10 -74
- package/dist/core/FilePublication.js.map +1 -1
- package/dist/core/FileSnapshot.js +36 -100
- package/dist/core/FileSnapshot.js.map +1 -1
- package/dist/core/HostPaths.d.ts +1 -0
- package/dist/core/HostPaths.d.ts.map +1 -1
- package/dist/core/HostPaths.js +11 -1
- package/dist/core/HostPaths.js.map +1 -1
- package/dist/core/KillHandler.js +3 -1
- package/dist/core/KillHandler.js.map +1 -1
- package/dist/core/LiveSubscription.js +1 -1
- package/dist/core/LiveSubscription.js.map +1 -1
- package/dist/core/LogBody.js +1 -1
- package/dist/core/LogBody.js.map +1 -1
- package/dist/core/LogWriter.js +1 -1
- package/dist/core/LogWriter.js.map +1 -1
- package/dist/core/LoopDriver.d.ts +3 -3
- package/dist/core/LoopDriver.d.ts.map +1 -1
- package/dist/core/LoopDriver.js +15 -13
- package/dist/core/LoopDriver.js.map +1 -1
- package/dist/core/LoopLifecycle.d.ts +5 -0
- package/dist/core/LoopLifecycle.d.ts.map +1 -1
- package/dist/core/LoopLifecycle.js +14 -1
- package/dist/core/LoopLifecycle.js.map +1 -1
- package/dist/core/LoopLifecycle.sql +11 -2
- package/dist/core/MembershipMaterialization.js +2 -2
- package/dist/core/MembershipMaterialization.js.map +1 -1
- package/dist/core/MessageResources.d.ts +3 -0
- package/dist/core/MessageResources.d.ts.map +1 -1
- package/dist/core/MessageResources.js +15 -1
- package/dist/core/MessageResources.js.map +1 -1
- package/dist/core/OperatorConfig.d.ts +1 -2
- package/dist/core/OperatorConfig.d.ts.map +1 -1
- package/dist/core/OperatorConfig.js +26 -92
- package/dist/core/OperatorConfig.js.map +1 -1
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +21 -18
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/PacketBuilder.sql +18 -5
- package/dist/core/ProblemLog.d.ts +1 -0
- package/dist/core/ProblemLog.d.ts.map +1 -1
- package/dist/core/ProblemLog.js +2 -2
- package/dist/core/ProblemLog.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts +1 -0
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +14 -8
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProposalPolicies.d.ts +4 -1
- package/dist/core/ProposalPolicies.d.ts.map +1 -1
- package/dist/core/ProposalPolicies.js +5 -2
- package/dist/core/ProposalPolicies.js.map +1 -1
- package/dist/core/ResourceMutations.js +2 -0
- package/dist/core/ResourceMutations.js.map +1 -1
- package/dist/core/ResourceSelector.js +1 -1
- package/dist/core/ResourceSelector.js.map +1 -1
- package/dist/core/SchemeRegistry.js +1 -1
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/ServiceHelp.js +1 -1
- package/dist/core/ServiceHelp.js.map +1 -1
- package/dist/core/ServiceTeardown.js +9 -12
- package/dist/core/ServiceTeardown.js.map +1 -1
- package/dist/core/StrikeRail.d.ts +2 -1
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +11 -6
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/TerminalResult.js +1 -1
- package/dist/core/TerminalResult.js.map +1 -1
- package/dist/core/TurnDispositionHandler.d.ts +1 -1
- package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
- package/dist/core/TurnDispositionHandler.js +8 -5
- package/dist/core/TurnDispositionHandler.js.map +1 -1
- package/dist/core/TurnMaterialization.js +6 -4
- package/dist/core/TurnMaterialization.js.map +1 -1
- package/dist/core/TurnMaterialization.sql +1 -1
- package/dist/core/TurnOps.js +1 -0
- package/dist/core/TurnOps.js.map +1 -1
- package/dist/core/TurnRunner.d.ts +4 -2
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +63 -51
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/WorkerControlHandler.d.ts +2 -0
- package/dist/core/WorkerControlHandler.d.ts.map +1 -1
- package/dist/core/WorkerControlHandler.js +84 -16
- package/dist/core/WorkerControlHandler.js.map +1 -1
- package/dist/core/WorkerName.d.ts +0 -4
- package/dist/core/WorkerName.d.ts.map +1 -1
- package/dist/core/WorkerName.js +5 -30
- package/dist/core/WorkerName.js.map +1 -1
- package/dist/core/WorkerName.sql +4 -20
- package/dist/core/WorkerOwners.d.ts +1 -0
- package/dist/core/WorkerOwners.d.ts.map +1 -1
- package/dist/core/WorkerOwners.js +6 -0
- package/dist/core/WorkerOwners.js.map +1 -1
- package/dist/core/WorkerOwners.sql +7 -0
- package/dist/core/WorkspaceGate.d.ts +3 -1
- package/dist/core/WorkspaceGate.d.ts.map +1 -1
- package/dist/core/WorkspaceGate.js +59 -10
- package/dist/core/WorkspaceGate.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +3 -0
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +14 -0
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/env-catalog.js +1 -1
- package/dist/core/env-catalog.js.map +1 -1
- package/dist/core/fork.js +1 -1
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +7 -6
- package/dist/core/mutation-types.d.ts +1 -0
- package/dist/core/mutation-types.d.ts.map +1 -1
- package/dist/core/mutation-types.js +4 -1
- package/dist/core/mutation-types.js.map +1 -1
- package/dist/core/namespace.js +1 -1
- package/dist/core/namespace.js.map +1 -1
- package/dist/core/packet-wire.d.ts +1 -1
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +24 -15
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +1 -1
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +1 -1
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/scheme-types.d.ts +5 -0
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/turn-signals.d.ts +4 -0
- package/dist/core/turn-signals.d.ts.map +1 -1
- package/dist/core/turn-signals.js +1 -0
- package/dist/core/turn-signals.js.map +1 -1
- package/dist/core/workspace-settings.js +1 -1
- package/dist/core/workspace-settings.js.map +1 -1
- package/dist/evidence/EvidenceReader.js +5 -5
- package/dist/evidence/EvidenceReader.js.map +1 -1
- package/dist/man/plurnk-service.1 +27 -6
- package/dist/schemes/Env.d.ts +7 -3
- package/dist/schemes/Env.d.ts.map +1 -1
- package/dist/schemes/Env.js +12 -6
- package/dist/schemes/Env.js.map +1 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +37 -25
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +3 -3
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/ExecutionInput.d.ts +2 -1
- package/dist/schemes/ExecutionInput.d.ts.map +1 -1
- package/dist/schemes/ExecutionInput.js +45 -7
- package/dist/schemes/ExecutionInput.js.map +1 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +32 -23
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +23 -5
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +24 -1
- package/dist/schemes/ResourceTreeScheme.js +2 -5
- package/dist/schemes/ResourceTreeScheme.js.map +1 -1
- package/dist/schemes/TurnSource.js +1 -1
- package/dist/schemes/TurnSource.js.map +1 -1
- package/dist/schemes/Worker.d.ts.map +1 -1
- package/dist/schemes/Worker.js +10 -3
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +2 -4
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +5 -3
- package/dist/schemes/_entry-find.js +3 -3
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +3 -3
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-manifest.sql +2 -0
- package/dist/schemes/_entry-ops.js +1 -1
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-send.js +1 -1
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/server/ClientReads.d.ts +1 -0
- package/dist/server/ClientReads.d.ts.map +1 -1
- package/dist/server/ClientReads.js +7 -2
- package/dist/server/ClientReads.js.map +1 -1
- package/dist/server/Credentials.d.ts +48 -0
- package/dist/server/Credentials.d.ts.map +1 -0
- package/dist/server/Credentials.js +203 -0
- package/dist/server/Credentials.js.map +1 -0
- package/dist/server/Daemon.d.ts +44 -39
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +287 -172
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DrainSupervisor.d.ts +8 -3
- package/dist/server/DrainSupervisor.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.js +44 -32
- package/dist/server/DrainSupervisor.js.map +1 -1
- package/dist/server/EnvFunctionality.d.ts.map +1 -1
- package/dist/server/EnvFunctionality.js +2 -1
- package/dist/server/EnvFunctionality.js.map +1 -1
- package/dist/server/Functionality.d.ts +4 -2
- package/dist/server/Functionality.d.ts.map +1 -1
- package/dist/server/Functionality.js +192 -96
- package/dist/server/Functionality.js.map +1 -1
- package/dist/server/FunctionalityManager.d.ts +1 -4
- package/dist/server/FunctionalityManager.d.ts.map +1 -1
- package/dist/server/FunctionalityManager.js +6 -6
- package/dist/server/FunctionalityManager.js.map +1 -1
- package/dist/server/HttpListener.d.ts +1 -1
- package/dist/server/HttpListener.d.ts.map +1 -1
- package/dist/server/HttpListener.js.map +1 -1
- package/dist/server/MembersFunctionality.d.ts +7 -8
- package/dist/server/MembersFunctionality.d.ts.map +1 -1
- package/dist/server/MembersFunctionality.js +90 -56
- package/dist/server/MembersFunctionality.js.map +1 -1
- package/dist/server/MessageAbandonment.d.ts +7 -0
- package/dist/server/MessageAbandonment.d.ts.map +1 -0
- package/dist/server/MessageAbandonment.js +57 -0
- package/dist/server/MessageAbandonment.js.map +1 -0
- package/dist/server/ModuleHost.d.ts +2 -1
- package/dist/server/ModuleHost.d.ts.map +1 -1
- package/dist/server/PlurnkSkill.js +4 -7
- package/dist/server/PlurnkSkill.js.map +1 -1
- package/dist/server/Rollback.d.ts +29 -0
- package/dist/server/Rollback.d.ts.map +1 -0
- package/dist/server/Rollback.js +111 -0
- package/dist/server/Rollback.js.map +1 -0
- package/dist/server/WorkerModelResolver.js +1 -0
- package/dist/server/WorkerModelResolver.js.map +1 -1
- package/dist/server/WorkerReferences.d.ts +14 -0
- package/dist/server/WorkerReferences.d.ts.map +1 -0
- package/dist/server/WorkerReferences.js +24 -0
- package/dist/server/WorkerReferences.js.map +1 -0
- package/dist/server/WorkerReferences.sql +31 -0
- package/dist/server/WorkspaceCapabilities.d.ts +2 -0
- package/dist/server/WorkspaceCapabilities.d.ts.map +1 -1
- package/dist/server/WorkspaceCapabilities.js +35 -18
- package/dist/server/WorkspaceCapabilities.js.map +1 -1
- package/dist/server/WorkspacePlugins.js +1 -0
- package/dist/server/WorkspacePlugins.js.map +1 -1
- package/dist/server/WorkspaceResidency.d.ts +3 -1
- package/dist/server/WorkspaceResidency.d.ts.map +1 -1
- package/dist/server/WorkspaceResidency.js +12 -5
- package/dist/server/WorkspaceResidency.js.map +1 -1
- package/dist/server/client-input.d.ts +2 -0
- package/dist/server/client-input.d.ts.map +1 -1
- package/dist/server/client-input.js +28 -0
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/drain.sql +46 -40
- package/dist/server/envelope.d.ts +5 -9
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +21 -84
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +17 -17
- package/dist/server/lifecycle-recovery.sql +5 -15
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +6 -0
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/logEntry.sql +94 -6
- package/dist/server/module-discovery.d.ts +1 -1
- package/dist/server/module-discovery.d.ts.map +1 -1
- package/dist/server/module-discovery.js +1 -0
- package/dist/server/module-discovery.js.map +1 -1
- package/dist/server/seam-loop.sql +15 -7
- package/dist/server/seam-proposal-list.sql +10 -4
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +7 -1
- package/dist/service.js.map +1 -1
- package/docs/members.md +20 -13
- package/migrations/002_workers.sql +2 -2
- package/migrations/006_log.sql +1 -1
- package/migrations/018_worker_owners.sql +1 -1
- package/migrations/026_message_obligations.sql +32 -0
- package/migrations/027_members_identity.sql +20 -0
- package/migrations/028_mutation_patch.sql +20 -0
- package/migrations/029_default_worker.sql +9 -0
- package/migrations/030_client_workers.sql +105 -0
- package/migrations/031_message_openers.sql +83 -0
- package/migrations/032_worker_references.sql +29 -0
- package/package.json +24 -24
- package/plurnk.nginx.conf +71 -0
- package/plurnk.service +2 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
var _a;
|
|
2
1
|
// {§functionality-coordinator} — the one owner of the workspace Functionality
|
|
3
2
|
// lifecycle above the family adapters (Agent Skills, MCP, outbound A2A). It owns
|
|
4
3
|
// durable workspace state, lifecycle ordering, serialization, atomic
|
|
@@ -42,7 +41,7 @@ const discoverInput = ({ inputs, emptyListsAll }) => ({
|
|
|
42
41
|
});
|
|
43
42
|
const failure = (family, code, status, detail, extensions = {}) => new OperationFailureError(Results.failure("functionality", code, status, detail, {}, { family, ...extensions }));
|
|
44
43
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
|
-
class Functionality {
|
|
44
|
+
export default class Functionality {
|
|
46
45
|
#host;
|
|
47
46
|
#adapters = new Map();
|
|
48
47
|
#owners = new Set();
|
|
@@ -93,31 +92,40 @@ class Functionality {
|
|
|
93
92
|
throw new Error(`Functionality ${family} input has no property map.`);
|
|
94
93
|
return { ...source, properties: { ...source.properties, ...scopeProperties } };
|
|
95
94
|
};
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
// {§functionality-addressing} — a family that names definitions by their content supplies the
|
|
96
|
+
// inputs of add and of the select verbs itself, and serves no replace.
|
|
97
|
+
const { addressing } = adapter;
|
|
98
|
+
if (addressing !== undefined && [addressing.add, addressing.select].some((schema) => !isRecord(schema) || !isRecord(schema.properties))) {
|
|
99
|
+
throw new Error(`Functionality family '${family}' declares addressing without object input schemas.`);
|
|
100
|
+
}
|
|
101
|
+
const addInput = addressing === undefined
|
|
102
|
+
? Object.freeze({
|
|
103
|
+
type: "object",
|
|
104
|
+
additionalProperties: false,
|
|
105
|
+
required: ["definition"],
|
|
106
|
+
properties: {
|
|
107
|
+
...scopeProperties,
|
|
108
|
+
alias: { type: "string", minLength: 1 },
|
|
109
|
+
definition: adapter.definitionSchema,
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
: scopedInput(addressing.add);
|
|
113
|
+
const selectInput = scopedInput(addressing?.select ?? ALIAS_INPUT);
|
|
106
114
|
// {§functionality-discover-advertisement} A family without discovery has no discover verb.
|
|
107
115
|
const schemas = Object.freeze({
|
|
108
116
|
list: scopedInput(EMPTY_INPUT),
|
|
109
117
|
...(discovery === undefined ? {} : { discover: scopedInput(discoverInput(discovery)) }),
|
|
110
118
|
add: addInput,
|
|
111
|
-
replace: { ...addInput, required: ["alias", "definition"] },
|
|
112
|
-
enable:
|
|
113
|
-
disable:
|
|
114
|
-
remove:
|
|
119
|
+
...(addressing === undefined ? { replace: { ...addInput, required: ["alias", "definition"] } } : {}),
|
|
120
|
+
enable: selectInput,
|
|
121
|
+
disable: selectInput,
|
|
122
|
+
remove: selectInput,
|
|
115
123
|
});
|
|
116
124
|
// {§functionality-model-projection} — the taught `add` example must satisfy the schema it teaches.
|
|
117
125
|
if (adapter.example !== undefined) {
|
|
118
126
|
const example = Validator.validateJsonSchemaInstance(addInput, adapter.example);
|
|
119
127
|
if (!example.valid) {
|
|
120
|
-
throw new Error(`Functionality family '${family}' teaches an add example that violates its own
|
|
128
|
+
throw new Error(`Functionality family '${family}' teaches an add example that violates its own add input: ${JSON.stringify(example.errors)}`);
|
|
121
129
|
}
|
|
122
130
|
}
|
|
123
131
|
this.#adapters.set(family, adapter);
|
|
@@ -144,7 +152,7 @@ class Functionality {
|
|
|
144
152
|
? "FunctionalityDiscoverResult"
|
|
145
153
|
: "FunctionalityMutationResult"),
|
|
146
154
|
handler: async (params, context) => {
|
|
147
|
-
if (context.scope === "
|
|
155
|
+
if (context.scope === "daemon" || context.scope !== scope)
|
|
148
156
|
throw new Error(`${scope}.${family}.${verb} requires a ${scope}-scoped context.`);
|
|
149
157
|
const identity = context.scope === "worker"
|
|
150
158
|
? { workspaceId: context.workspaceId, workerId: context.workerId, scope }
|
|
@@ -167,14 +175,16 @@ class Functionality {
|
|
|
167
175
|
// `ifChanged` republishes only when the enabled definitions differ from the ones the resident
|
|
168
176
|
// publication prepared ({§functionality-hotload}).
|
|
169
177
|
async refresh(family, identity, options = {}) {
|
|
178
|
+
this.#host.signal.throwIfAborted();
|
|
170
179
|
const adapter = this.#adapter(family);
|
|
171
180
|
await this.#withWorkspaceGate(identity.workspaceId, adapter.namespaceOwner, options.gate ?? "wait", () => this.#serialize(this.#key(identity.workspaceId, family), async () => {
|
|
181
|
+
this.#host.signal.throwIfAborted();
|
|
172
182
|
const current = this.#families.get(this.#key(identity.workspaceId, family));
|
|
173
183
|
if (current === undefined)
|
|
174
184
|
return;
|
|
175
185
|
if (options.ifChanged === true && current.configurationFailure === null) {
|
|
176
186
|
try {
|
|
177
|
-
if (
|
|
187
|
+
if (Functionality.#sameDefinitions(await this.#publishable(adapter, identity, current.state), current.enabled))
|
|
178
188
|
return;
|
|
179
189
|
}
|
|
180
190
|
catch (cause) {
|
|
@@ -261,14 +271,21 @@ class Functionality {
|
|
|
261
271
|
return out;
|
|
262
272
|
}
|
|
263
273
|
// The one lifecycle entry for both projections ({§functionality-model-mutation}).
|
|
264
|
-
async invoke(family, verb, params, identity, caller, options = {}) {
|
|
274
|
+
async invoke(family, verb, params, identity, caller, options = {}, signal = this.#host.signal) {
|
|
275
|
+
const lifetime = signal === this.#host.signal ? signal : AbortSignal.any([this.#host.signal, signal]);
|
|
276
|
+
lifetime.throwIfAborted();
|
|
277
|
+
const pending = this.#invoke(family, verb, params, identity, caller, options, lifetime);
|
|
278
|
+
this.#admissions.set(pending, identity.workspaceId);
|
|
265
279
|
try {
|
|
266
|
-
return await
|
|
280
|
+
return await pending;
|
|
267
281
|
}
|
|
268
282
|
catch (cause) {
|
|
269
283
|
if (!(cause instanceof ConfigurationError))
|
|
270
284
|
throw cause;
|
|
271
|
-
throw
|
|
285
|
+
throw Functionality.#configurationFailure(family, cause);
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
this.#admissions.delete(pending);
|
|
272
289
|
}
|
|
273
290
|
}
|
|
274
291
|
static #configurationFailure(family, cause) {
|
|
@@ -279,7 +296,7 @@ class Functionality {
|
|
|
279
296
|
recovery: "Correct the named configuration input. Other capabilities remain available.",
|
|
280
297
|
}), { cause });
|
|
281
298
|
}
|
|
282
|
-
async #invoke(family, verb, params, identity, caller, options) {
|
|
299
|
+
async #invoke(family, verb, params, identity, caller, options, signal) {
|
|
283
300
|
const adapter = this.#adapter(family);
|
|
284
301
|
const schema = this.#schemas.get(family)[verb];
|
|
285
302
|
// Neither projection offers a verb the family does not serve ({§functionality-discover-advertisement}).
|
|
@@ -308,12 +325,19 @@ class Functionality {
|
|
|
308
325
|
// Worker's own lane and take no workspace exclusivity: a Worker shapes its environment while
|
|
309
326
|
// its siblings run.
|
|
310
327
|
if (scope === "worker") {
|
|
311
|
-
const workerId =
|
|
312
|
-
return this.#serialize(`${this.#key(identity.workspaceId, family)}:${workerId}`, () => this.#invokeForWorker(adapter, verb, input, identity, workerId, caller, options));
|
|
328
|
+
const workerId = Functionality.#workerOf(adapter, identity);
|
|
329
|
+
return this.#serialize(`${this.#key(identity.workspaceId, family)}:${workerId}`, () => this.#invokeForWorker(adapter, verb, input, identity, workerId, caller, options, signal));
|
|
313
330
|
}
|
|
314
331
|
if (verb === "list")
|
|
315
332
|
return { status: 200, body: await this.#list(adapter, identity) };
|
|
316
|
-
|
|
333
|
+
const mutate = () => this.#serialize(this.#key(identity.workspaceId, family), () => this.#mutate(adapter, verb, input, identity, caller, options, signal));
|
|
334
|
+
// A context family (one that declares the worker scope) is read where it is used, so its workspace
|
|
335
|
+
// defaults change nothing resident either: they serialize on the family's lane, as a Worker's own do,
|
|
336
|
+
// and an operation inside its own held turn never waits on itself.
|
|
337
|
+
if (scopesOf(adapter).includes("worker"))
|
|
338
|
+
return mutate();
|
|
339
|
+
const publish = () => this.#withWorkspaceGate(identity.workspaceId, adapter.namespaceOwner, caller === "operation" ? "wait" : "try", mutate, signal);
|
|
340
|
+
return caller === "operation" ? this.#host.atWorkspaceBoundary(identity.workspaceId, publish) : publish();
|
|
317
341
|
}
|
|
318
342
|
// A worker-scoped verb that names no Worker is a defect at the projection, never a client
|
|
319
343
|
// failure: both projections name the Worker ({§functionality-model-projection}).
|
|
@@ -342,8 +366,8 @@ class Functionality {
|
|
|
342
366
|
this.#queues.set(key, next);
|
|
343
367
|
return next;
|
|
344
368
|
}
|
|
345
|
-
async #withWorkspaceGate(workspaceId, owner, gate, run) {
|
|
346
|
-
const pending = this.#host.withWorkspaceGate(workspaceId, owner, gate, run);
|
|
369
|
+
async #withWorkspaceGate(workspaceId, owner, gate, run, signal = this.#host.signal) {
|
|
370
|
+
const pending = this.#host.withWorkspaceGate(workspaceId, owner, gate, run, signal);
|
|
347
371
|
this.#admissions.set(pending, workspaceId);
|
|
348
372
|
try {
|
|
349
373
|
return await pending;
|
|
@@ -355,6 +379,7 @@ class Functionality {
|
|
|
355
379
|
async #activate(adapter, context) {
|
|
356
380
|
const identity = { workspaceId: context.workspaceId };
|
|
357
381
|
await this.#serialize(this.#key(identity.workspaceId, adapter.family), async () => {
|
|
382
|
+
this.#host.signal.throwIfAborted();
|
|
358
383
|
const state = await this.#loadState(adapter, identity.workspaceId);
|
|
359
384
|
await this.#publish(adapter, identity, state, { failure: "publish-unavailable", configurationFailure: "contain", retain: context.retain });
|
|
360
385
|
});
|
|
@@ -370,10 +395,10 @@ class Functionality {
|
|
|
370
395
|
});
|
|
371
396
|
}
|
|
372
397
|
async #loadState(adapter, workspaceId) {
|
|
373
|
-
return
|
|
398
|
+
return Functionality.#parseState(adapter, await this.#host.readWorkspaceModuleState(workspaceId, adapter.namespaceOwner), `workspace ${workspaceId}`, "workspace");
|
|
374
399
|
}
|
|
375
400
|
async #loadWorkerState(adapter, workerId) {
|
|
376
|
-
return
|
|
401
|
+
return Functionality.#parseState(adapter, await this.#host.readWorkerModuleState(workerId, adapter.namespaceOwner), `worker ${workerId}`, "worker");
|
|
377
402
|
}
|
|
378
403
|
static #parseState(adapter, raw, where, scope) {
|
|
379
404
|
if (raw === null)
|
|
@@ -415,7 +440,7 @@ class Functionality {
|
|
|
415
440
|
}
|
|
416
441
|
async #effective(adapter, identity, state) {
|
|
417
442
|
const effective = new Map();
|
|
418
|
-
const local =
|
|
443
|
+
const local = Functionality.#localOrigin(identity);
|
|
419
444
|
const inheritsWorkspace = identity.scope === "worker" && scopesOf(adapter).includes("workspace");
|
|
420
445
|
const lower = inheritsWorkspace
|
|
421
446
|
? [...(await this.#effective(adapter, { workspaceId: identity.workspaceId, scope: "workspace" }, await this.#loadState(adapter, identity.workspaceId))).values()]
|
|
@@ -471,7 +496,7 @@ class Functionality {
|
|
|
471
496
|
return Validator.assertFunctionalityListResult({
|
|
472
497
|
family: adapter.family,
|
|
473
498
|
definitions: [...effective.values()].map((definition) => {
|
|
474
|
-
const outcome = isDeepStrictEqual(family?.enabled.get(definition.alias),
|
|
499
|
+
const outcome = isDeepStrictEqual(family?.enabled.get(definition.alias), Functionality.#binding(definition))
|
|
475
500
|
? outcomes?.get(definition.alias)
|
|
476
501
|
: undefined;
|
|
477
502
|
return this.#projection(definition, outcome ?? { state: "dormant" });
|
|
@@ -483,45 +508,66 @@ class Functionality {
|
|
|
483
508
|
const candidates = await adapter.discover(query, identity, options);
|
|
484
509
|
return Validator.assertFunctionalityDiscoverResult({ family: adapter.family, candidates: [...candidates] });
|
|
485
510
|
}
|
|
511
|
+
// The alias a select verb names: the input's `alias`, or what an addressing family reads from its
|
|
512
|
+
// own select input ({§functionality-addressing}).
|
|
513
|
+
static #selected(adapter, input) {
|
|
514
|
+
return adapter.addressing === undefined ? input.alias : adapter.addressing.alias(input);
|
|
515
|
+
}
|
|
486
516
|
// The verb semantics, one implementation for every family ({§functionality-scope}): the next
|
|
487
|
-
// state, the
|
|
517
|
+
// state, the aliases it concerns, and how the mutation reports. Only publication differs by scope.
|
|
488
518
|
async #transition(adapter, verb, input, identity, caller, state, effective, options) {
|
|
489
|
-
const local =
|
|
519
|
+
const local = Functionality.#localOrigin(identity);
|
|
490
520
|
const here = local === "worker" ? "this worker" : "this workspace";
|
|
491
521
|
const definitions = { ...state.definitions };
|
|
492
|
-
|
|
522
|
+
const aliases = [];
|
|
493
523
|
let status = 200;
|
|
494
524
|
let removed = false;
|
|
495
|
-
let
|
|
525
|
+
let admissions = [];
|
|
496
526
|
switch (verb) {
|
|
497
527
|
case "add":
|
|
498
528
|
case "replace": {
|
|
499
529
|
const admitted = await adapter.admit(input, identity, caller, options);
|
|
500
|
-
|
|
501
|
-
alias
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
530
|
+
admissions = Array.isArray(admitted) ? [...admitted] : [admitted];
|
|
531
|
+
// {§functionality-addressing} — an alias family admits exactly one definition; an addressing
|
|
532
|
+
// family's add admits each the body names, and all of them or none.
|
|
533
|
+
if (adapter.addressing === undefined ? admissions.length !== 1 : admissions.length === 0) {
|
|
534
|
+
throw new Error(`${adapter.family} admitted ${admissions.length} definitions for one ${verb}.`);
|
|
535
|
+
}
|
|
536
|
+
for (const admission of admissions) {
|
|
537
|
+
const { alias } = admission;
|
|
538
|
+
if (!aliasPattern(adapter).test(alias))
|
|
539
|
+
throw failure(adapter.family, "alias-invalid", 400, `Alias '${alias}' must match ${aliasPattern(adapter)}.`, { alias, retryable: false });
|
|
540
|
+
if (aliases.includes(alias))
|
|
541
|
+
throw new Error(`${adapter.family} admitted '${alias}' twice in one ${verb}.`);
|
|
542
|
+
const current = effective.get(alias);
|
|
543
|
+
if (verb === "add" && current?.origin === local && !isDeepStrictEqual(current.definition, admission.definition)) {
|
|
544
|
+
throw failure(adapter.family, "alias-exists", 409, `'${alias}' already has a different ${local} definition.`, {
|
|
545
|
+
alias,
|
|
546
|
+
recovery: adapter.addressing === undefined ? "Use replace with the complete definition to change it." : "Remove it, then add it again.",
|
|
547
|
+
retryable: false,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
definitions[alias] = { origin: local, definition: admission.definition, enabled: verb === "replace" ? current?.enabled ?? true : true };
|
|
551
|
+
if (current?.origin !== local)
|
|
552
|
+
status = 201;
|
|
553
|
+
aliases.push(alias);
|
|
507
554
|
}
|
|
508
|
-
definitions[alias] = { origin: local, definition: admitted.definition, enabled: verb === "replace" ? current?.enabled ?? true : true };
|
|
509
|
-
status = current?.origin === local ? 200 : 201;
|
|
510
555
|
break;
|
|
511
556
|
}
|
|
512
557
|
case "enable":
|
|
513
558
|
case "disable": {
|
|
514
|
-
alias = input
|
|
559
|
+
const alias = Functionality.#selected(adapter, input);
|
|
515
560
|
const current = effective.get(alias);
|
|
516
561
|
if (current === undefined)
|
|
517
562
|
throw failure(adapter.family, "alias-unknown", 404, `'${alias}' is not available to ${here}.`, { alias, retryable: false });
|
|
518
563
|
definitions[alias] = current.origin === local
|
|
519
564
|
? { origin: local, definition: current.definition, enabled: verb === "enable" }
|
|
520
565
|
: { origin: current.origin, enabled: verb === "enable" };
|
|
566
|
+
aliases.push(alias);
|
|
521
567
|
break;
|
|
522
568
|
}
|
|
523
569
|
case "remove": {
|
|
524
|
-
alias = input
|
|
570
|
+
const alias = Functionality.#selected(adapter, input);
|
|
525
571
|
const current = effective.get(alias);
|
|
526
572
|
if (current === undefined)
|
|
527
573
|
throw failure(adapter.family, "alias-unknown", 404, `'${alias}' is not available to ${here}.`, { alias, retryable: false });
|
|
@@ -532,25 +578,34 @@ class Functionality {
|
|
|
532
578
|
await adapter.forget?.({ alias, definition: current.definition }, identity);
|
|
533
579
|
delete definitions[alias];
|
|
534
580
|
removed = true;
|
|
581
|
+
aliases.push(alias);
|
|
535
582
|
break;
|
|
536
583
|
}
|
|
537
584
|
default: throw new Error(`unreachable verb ${verb}`);
|
|
538
585
|
}
|
|
539
|
-
return { definitions,
|
|
586
|
+
return { definitions, aliases, status, removed, admissions };
|
|
540
587
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
alias,
|
|
548
|
-
...(projection === undefined ? {} : { definition: projection }),
|
|
549
|
-
...(removed ? { removed: true } : {}),
|
|
588
|
+
// {§functionality-mutation-status} — the receipt: the one alias a mutation concerned, or every
|
|
589
|
+
// definition an addressing family's add admitted ({§functionality-addressing}).
|
|
590
|
+
#mutationResult(adapter, verb, { aliases, status, removed }, effectiveAfter, outcomes) {
|
|
591
|
+
const projections = aliases.map((alias) => {
|
|
592
|
+
const definition = effectiveAfter.get(alias);
|
|
593
|
+
return definition === undefined ? undefined : this.#projection(definition, outcomes.get(alias));
|
|
550
594
|
});
|
|
595
|
+
const authorization = projections.some((projection) => projection?.state === "authorization-required");
|
|
596
|
+
const body = Validator.assertFunctionalityMutationResult(adapter.addressing !== undefined && verb === "add"
|
|
597
|
+
? { status: authorization ? 202 : status, family: adapter.family, definitions: projections.map((projection) => projection) }
|
|
598
|
+
: {
|
|
599
|
+
status: authorization ? 202 : status,
|
|
600
|
+
family: adapter.family,
|
|
601
|
+
alias: aliases[0],
|
|
602
|
+
...(projections[0] === undefined ? {} : { definition: projections[0] }),
|
|
603
|
+
...(removed ? { removed: true } : {}),
|
|
604
|
+
});
|
|
551
605
|
return { status: body.status, body };
|
|
552
606
|
}
|
|
553
|
-
async #mutate(adapter, verb, input, identity, caller, options) {
|
|
607
|
+
async #mutate(adapter, verb, input, identity, caller, options, signal) {
|
|
608
|
+
signal.throwIfAborted();
|
|
554
609
|
const key = this.#key(identity.workspaceId, adapter.family);
|
|
555
610
|
const family = this.#families.get(key);
|
|
556
611
|
if (family === undefined)
|
|
@@ -560,25 +615,33 @@ class Functionality {
|
|
|
560
615
|
const nextState = { version: STATE_VERSION, definitions: transition.definitions };
|
|
561
616
|
// Re-enabling an alias that is not active retries its preparation; an
|
|
562
617
|
// already-active alias keeps its live attachment.
|
|
563
|
-
const retry = verb === "enable" && family.prepared?.outcomes.get(transition.
|
|
618
|
+
const retry = verb === "enable" && family.prepared?.outcomes.get(transition.aliases[0])?.state !== "active";
|
|
564
619
|
const publication = await this.#publish(adapter, identity, nextState, {
|
|
565
620
|
failure: caller === "action" || verb === "replace" ? "reject" : "publish-unavailable",
|
|
566
621
|
configurationFailure: "reject",
|
|
567
622
|
retain: () => this.#host.retainWorkspace(identity.workspaceId),
|
|
568
|
-
forceAlias: retry ? transition.
|
|
569
|
-
|
|
623
|
+
forceAlias: retry ? transition.aliases[0] : null,
|
|
624
|
+
admissions: transition.admissions,
|
|
625
|
+
signal,
|
|
570
626
|
});
|
|
571
627
|
const effectiveAfter = await this.#effective(adapter, identity, nextState);
|
|
572
|
-
return this.#mutationResult(adapter, transition, effectiveAfter, publication.outcomes);
|
|
628
|
+
return this.#mutationResult(adapter, verb, transition, effectiveAfter, publication.outcomes);
|
|
573
629
|
}
|
|
574
630
|
// {§functionality-scope} — the Worker's own lane. State is read at each verb rather than held: the
|
|
575
631
|
// spawn reads the same row, and a Worker's row is written at its creation, so nothing in memory
|
|
576
632
|
// could be more current than the table.
|
|
577
|
-
async #invokeForWorker(adapter, verb, input, identity, workerId, caller, options) {
|
|
633
|
+
async #invokeForWorker(adapter, verb, input, identity, workerId, caller, options, signal) {
|
|
634
|
+
signal.throwIfAborted();
|
|
578
635
|
const state = await this.#loadWorkerState(adapter, workerId);
|
|
579
636
|
const effective = await this.#effective(adapter, identity, state);
|
|
580
637
|
if (verb === "list") {
|
|
581
|
-
const prepared = await this.#prepareForWorker(adapter, identity, effective, "publish-unavailable");
|
|
638
|
+
const prepared = await this.#prepareForWorker(adapter, identity, effective, "publish-unavailable", [], signal);
|
|
639
|
+
try {
|
|
640
|
+
signal.throwIfAborted();
|
|
641
|
+
}
|
|
642
|
+
catch (cause) {
|
|
643
|
+
return Functionality.#abort(prepared, cause);
|
|
644
|
+
}
|
|
582
645
|
await prepared.commit();
|
|
583
646
|
return { status: 200, body: Validator.assertFunctionalityListResult({
|
|
584
647
|
family: adapter.family,
|
|
@@ -588,35 +651,35 @@ class Functionality {
|
|
|
588
651
|
const transition = await this.#transition(adapter, verb, input, identity, caller, state, effective, options);
|
|
589
652
|
const nextState = { version: STATE_VERSION, definitions: transition.definitions };
|
|
590
653
|
const effectiveAfter = await this.#effective(adapter, identity, nextState);
|
|
591
|
-
const prepared = await this.#prepareForWorker(adapter, identity, effectiveAfter, caller === "action" || verb === "replace" ? "reject" : "publish-unavailable", transition.
|
|
654
|
+
const prepared = await this.#prepareForWorker(adapter, identity, effectiveAfter, caller === "action" || verb === "replace" ? "reject" : "publish-unavailable", transition.admissions, signal);
|
|
592
655
|
try {
|
|
593
|
-
|
|
656
|
+
signal.throwIfAborted();
|
|
657
|
+
await this.#host.replaceWorkerModuleState(workerId, adapter.namespaceOwner, Functionality.#persisted(nextState));
|
|
594
658
|
}
|
|
595
659
|
catch (cause) {
|
|
596
|
-
return
|
|
660
|
+
return Functionality.#abort(prepared, cause);
|
|
597
661
|
}
|
|
598
662
|
await prepared.commit();
|
|
599
|
-
return this.#mutationResult(adapter, transition, effectiveAfter, prepared.outcomes);
|
|
663
|
+
return this.#mutationResult(adapter, verb, transition, effectiveAfter, prepared.outcomes);
|
|
600
664
|
}
|
|
601
665
|
// A worker-scoped family's preparation yields outcomes only. It holds no residency — no runtimes,
|
|
602
666
|
// documents or snapshot ({§module-workspace-residency}) — because its definitions are read at the
|
|
603
667
|
// spawn that uses them rather than leased and cooled; the coordinator enforces that here.
|
|
604
|
-
async #prepareForWorker(adapter, identity, effective, failureMode,
|
|
605
|
-
const enabled =
|
|
606
|
-
const prepared = await
|
|
607
|
-
workspaceId: identity.workspaceId, enabled, previous: null, failure: failureMode,
|
|
608
|
-
...(admission === undefined ? {} : { admission }),
|
|
668
|
+
async #prepareForWorker(adapter, identity, effective, failureMode, admissions = [], signal = this.#host.signal) {
|
|
669
|
+
const enabled = Functionality.#enabled(effective);
|
|
670
|
+
const prepared = await this.#prepare(adapter, {
|
|
671
|
+
workspaceId: identity.workspaceId, enabled, previous: null, failure: failureMode, admissions,
|
|
609
672
|
progress: () => undefined,
|
|
610
673
|
retain: () => this.#host.retainWorkspace(identity.workspaceId),
|
|
611
|
-
});
|
|
674
|
+
}, signal);
|
|
612
675
|
try {
|
|
613
|
-
|
|
676
|
+
Functionality.#checkOutcomes(adapter, enabled, prepared);
|
|
614
677
|
if ((prepared.runtimes?.length ?? 0) > 0 || prepared.documents.length > 0 || prepared.snapshot !== null) {
|
|
615
678
|
throw new Error(`${adapter.family} is worker-scoped and holds no residency, yet its preparation published runtimes, documents or a snapshot.`);
|
|
616
679
|
}
|
|
617
680
|
}
|
|
618
681
|
catch (cause) {
|
|
619
|
-
return
|
|
682
|
+
return Functionality.#abort(prepared, cause);
|
|
620
683
|
}
|
|
621
684
|
return prepared;
|
|
622
685
|
}
|
|
@@ -626,7 +689,7 @@ class Functionality {
|
|
|
626
689
|
async #publishable(adapter, identity, state) {
|
|
627
690
|
if (!scopesOf(adapter).includes("workspace"))
|
|
628
691
|
return new Map();
|
|
629
|
-
return
|
|
692
|
+
return Functionality.#enabled(await this.#effective(adapter, { ...identity, scope: "workspace" }, state));
|
|
630
693
|
}
|
|
631
694
|
static #sameDefinitions(left, right) {
|
|
632
695
|
if (left.size !== right.size)
|
|
@@ -644,7 +707,7 @@ class Functionality {
|
|
|
644
707
|
const enabled = new Map();
|
|
645
708
|
for (const definition of effective.values()) {
|
|
646
709
|
if (definition.enabled)
|
|
647
|
-
enabled.set(definition.alias, structuredClone(
|
|
710
|
+
enabled.set(definition.alias, structuredClone(Functionality.#binding(definition)));
|
|
648
711
|
}
|
|
649
712
|
return enabled;
|
|
650
713
|
}
|
|
@@ -662,19 +725,36 @@ class Functionality {
|
|
|
662
725
|
return [...(this.#preparations.get(workspaceId)?.values() ?? [])].map((activity) => ({ ...activity }));
|
|
663
726
|
}
|
|
664
727
|
async #publish(adapter, identity, nextState, options) {
|
|
728
|
+
(options.signal ?? this.#host.signal).throwIfAborted();
|
|
665
729
|
const { workspaceId } = identity;
|
|
666
730
|
const current = this.#preparations.get(workspaceId) ?? new Map();
|
|
667
731
|
this.#preparations.set(workspaceId, current);
|
|
668
|
-
const
|
|
669
|
-
|
|
732
|
+
const forget = () => {
|
|
733
|
+
for (const [key, activity] of current)
|
|
734
|
+
if (activity.family === adapter.family)
|
|
735
|
+
current.delete(key);
|
|
736
|
+
};
|
|
737
|
+
// {§functionality-preparation-visibility} — one activity for the family, or one per alias it reports
|
|
738
|
+
// preparing, each keeping the time its alias began.
|
|
739
|
+
const report = (aliases, phase) => {
|
|
740
|
+
const now = new Date().toISOString();
|
|
741
|
+
const began = new Map([...current.values()]
|
|
742
|
+
.filter((activity) => activity.family === adapter.family && activity.alias !== null)
|
|
743
|
+
.map((activity) => [activity.alias, activity.since]));
|
|
744
|
+
forget();
|
|
745
|
+
if (aliases.length === 0)
|
|
746
|
+
current.set(adapter.family, { family: adapter.family, alias: null, phase, since: now });
|
|
747
|
+
for (const alias of aliases) {
|
|
748
|
+
current.set(`${adapter.family}/${alias}`, { family: adapter.family, alias, phase, since: began.get(alias) ?? now });
|
|
749
|
+
}
|
|
670
750
|
this.#host.preparationChanged(workspaceId, this.preparationStatus(workspaceId));
|
|
671
751
|
};
|
|
672
|
-
report(
|
|
752
|
+
report([], "preparing");
|
|
673
753
|
try {
|
|
674
754
|
return await this.#publishPrepared(adapter, identity, nextState, options, report);
|
|
675
755
|
}
|
|
676
756
|
finally {
|
|
677
|
-
|
|
757
|
+
forget();
|
|
678
758
|
if (current.size === 0)
|
|
679
759
|
this.#preparations.delete(workspaceId);
|
|
680
760
|
this.#host.preparationChanged(workspaceId, this.preparationStatus(workspaceId));
|
|
@@ -682,6 +762,7 @@ class Functionality {
|
|
|
682
762
|
}
|
|
683
763
|
async #publishPrepared(adapter, identity, nextState, options, report) {
|
|
684
764
|
const key = this.#key(identity.workspaceId, adapter.family);
|
|
765
|
+
const signal = options.signal ?? this.#host.signal;
|
|
685
766
|
const previous = this.#families.get(key)?.prepared ?? null;
|
|
686
767
|
let enabled = new Map();
|
|
687
768
|
let prepared = null;
|
|
@@ -690,20 +771,23 @@ class Functionality {
|
|
|
690
771
|
try {
|
|
691
772
|
enabled = await this.#publishable(adapter, identity, nextState);
|
|
692
773
|
phase = "preparation";
|
|
693
|
-
prepared = await
|
|
774
|
+
prepared = await this.#prepare(adapter, {
|
|
694
775
|
workspaceId: identity.workspaceId,
|
|
695
776
|
enabled,
|
|
696
777
|
previous: previous?.snapshot ?? null,
|
|
697
778
|
failure: options.failure,
|
|
698
|
-
|
|
699
|
-
progress: (
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
779
|
+
admissions: options.admissions ?? [],
|
|
780
|
+
progress: (aliases) => {
|
|
781
|
+
signal.throwIfAborted();
|
|
782
|
+
for (const alias of aliases) {
|
|
783
|
+
if (!enabled.has(alias))
|
|
784
|
+
throw new Error(`${adapter.family} reported preparation of unknown alias '${alias}'.`);
|
|
785
|
+
}
|
|
786
|
+
report(aliases, "preparing");
|
|
703
787
|
},
|
|
704
788
|
retain: options.retain,
|
|
705
789
|
...(options.forceAlias ? { force: options.forceAlias } : {}),
|
|
706
|
-
});
|
|
790
|
+
}, signal);
|
|
707
791
|
}
|
|
708
792
|
catch (cause) {
|
|
709
793
|
if (!(cause instanceof ConfigurationError) || options.configurationFailure === "reject")
|
|
@@ -711,11 +795,12 @@ class Functionality {
|
|
|
711
795
|
configurationFailure = { phase, error: cause };
|
|
712
796
|
enabled = new Map();
|
|
713
797
|
}
|
|
714
|
-
report(null, "publishing");
|
|
715
798
|
let runtimes;
|
|
716
799
|
try {
|
|
800
|
+
signal.throwIfAborted();
|
|
801
|
+
report([], "publishing");
|
|
717
802
|
if (prepared !== null)
|
|
718
|
-
|
|
803
|
+
Functionality.#checkOutcomes(adapter, enabled, prepared);
|
|
719
804
|
for (const runtime of prepared?.runtimes ?? []) {
|
|
720
805
|
if (runtime.namespaceOwner !== adapter.namespaceOwner) {
|
|
721
806
|
throw new Error(`${adapter.family} prepared a runtime owned by '${runtime.namespaceOwner}' instead of '${adapter.namespaceOwner}'.`);
|
|
@@ -736,15 +821,16 @@ class Functionality {
|
|
|
736
821
|
runtimes = [manager, ...(prepared?.runtimes ?? [])];
|
|
737
822
|
}
|
|
738
823
|
catch (cause) {
|
|
739
|
-
return
|
|
824
|
+
return Functionality.#abort(prepared, cause);
|
|
740
825
|
}
|
|
741
826
|
const commit = async () => {
|
|
742
827
|
const before = this.#families.get(key);
|
|
743
828
|
try {
|
|
829
|
+
signal.throwIfAborted();
|
|
744
830
|
await this.#host.replaceWorkspaceCapabilities({
|
|
745
831
|
workspaceId: identity.workspaceId,
|
|
746
832
|
namespaceOwner: adapter.namespaceOwner,
|
|
747
|
-
state:
|
|
833
|
+
state: Functionality.#persisted(nextState),
|
|
748
834
|
runtimes,
|
|
749
835
|
}, {
|
|
750
836
|
gate: "none",
|
|
@@ -760,7 +846,7 @@ class Functionality {
|
|
|
760
846
|
});
|
|
761
847
|
}
|
|
762
848
|
catch (cause) {
|
|
763
|
-
return
|
|
849
|
+
return Functionality.#abort(prepared, cause);
|
|
764
850
|
}
|
|
765
851
|
if (prepared !== null)
|
|
766
852
|
await prepared.commit();
|
|
@@ -770,6 +856,18 @@ class Functionality {
|
|
|
770
856
|
await commit();
|
|
771
857
|
return { outcomes: prepared?.outcomes ?? new Map() };
|
|
772
858
|
}
|
|
859
|
+
// {§functionality-preparation-cancellation} Join the adapter; abort a returned candidate before publication.
|
|
860
|
+
async #prepare(adapter, preparation, signal = this.#host.signal) {
|
|
861
|
+
signal.throwIfAborted();
|
|
862
|
+
const prepared = await adapter.prepare({ ...preparation, signal });
|
|
863
|
+
try {
|
|
864
|
+
signal.throwIfAborted();
|
|
865
|
+
}
|
|
866
|
+
catch (cause) {
|
|
867
|
+
return Functionality.#abort(prepared, cause);
|
|
868
|
+
}
|
|
869
|
+
return prepared;
|
|
870
|
+
}
|
|
773
871
|
static async #abort(prepared, cause) {
|
|
774
872
|
try {
|
|
775
873
|
await prepared?.abort();
|
|
@@ -780,6 +878,4 @@ class Functionality {
|
|
|
780
878
|
throw cause;
|
|
781
879
|
}
|
|
782
880
|
}
|
|
783
|
-
_a = Functionality;
|
|
784
|
-
export default Functionality;
|
|
785
881
|
//# sourceMappingURL=Functionality.js.map
|