@hypequery/deployment 0.1.0 → 0.3.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/README.md +182 -2
- package/dist/activation.d.ts +62 -0
- package/dist/activation.d.ts.map +1 -0
- package/dist/activation.js +611 -0
- package/dist/control-plane-adapters.d.ts +7 -0
- package/dist/control-plane-adapters.d.ts.map +1 -0
- package/dist/control-plane-adapters.js +163 -0
- package/dist/control-plane-limits.d.ts +7 -0
- package/dist/control-plane-limits.d.ts.map +1 -0
- package/dist/control-plane-limits.js +18 -0
- package/dist/control-plane.d.ts +35 -0
- package/dist/control-plane.d.ts.map +1 -0
- package/dist/control-plane.js +362 -0
- package/dist/filesystem-store.d.ts +25 -0
- package/dist/filesystem-store.d.ts.map +1 -0
- package/dist/filesystem-store.js +418 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/node-runtime-factory.d.ts +16 -0
- package/dist/node-runtime-factory.d.ts.map +1 -0
- package/dist/node-runtime-factory.js +274 -0
- package/dist/runtime-materialization.d.ts +58 -0
- package/dist/runtime-materialization.d.ts.map +1 -0
- package/dist/runtime-materialization.js +187 -0
- package/dist/runtime-supervisor.d.ts +70 -0
- package/dist/runtime-supervisor.d.ts.map +1 -0
- package/dist/runtime-supervisor.js +254 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @hypequery/deployment
|
|
2
2
|
|
|
3
|
-
Provider-neutral verification
|
|
4
|
-
bundles.
|
|
3
|
+
Provider-neutral verification, intake, activation, and HTTP control-plane
|
|
4
|
+
building blocks for Hypequery deployment bundles.
|
|
5
5
|
|
|
6
6
|
The package accepts the authenticated multipart transport emitted by
|
|
7
7
|
`hypequery deploy`, reconstructs only manifest-declared files in temporary
|
|
@@ -65,4 +65,184 @@ filesystem. It rejects symbolic links and undeclared entries, enforces byte
|
|
|
65
65
|
limits, recomputes every hash and identity, and returns an immutable verified
|
|
66
66
|
snapshot.
|
|
67
67
|
|
|
68
|
+
## Filesystem store
|
|
69
|
+
|
|
70
|
+
`createFileSystemDeploymentSubmissionStore` is the reference durable store for
|
|
71
|
+
a single host. It copies verified bundle bytes out of temporary intake storage,
|
|
72
|
+
revalidates the copy, and atomically publishes the release only after its bundle
|
|
73
|
+
is durable. Replaying the same release returns `already-exists` after the stored
|
|
74
|
+
state has been fully revalidated.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import {
|
|
78
|
+
createDeploymentIntake,
|
|
79
|
+
createFileSystemDeploymentSubmissionStore,
|
|
80
|
+
} from '@hypequery/deployment';
|
|
81
|
+
|
|
82
|
+
const store = createFileSystemDeploymentSubmissionStore({
|
|
83
|
+
directory: '/var/lib/hypequery/deployments',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const intake = createDeploymentIntake({
|
|
87
|
+
authenticator,
|
|
88
|
+
authorizer,
|
|
89
|
+
store,
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The closed layout is content-addressed:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
<directory>/
|
|
97
|
+
bundles/<bundle identity>/...
|
|
98
|
+
releases/<release identity>/release.json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Bundle directories are published before release directories. A crash can leave
|
|
102
|
+
an unreferenced bundle that a later submission safely reuses, but cannot expose
|
|
103
|
+
a release whose bundle is incomplete. Files and directories are opened without
|
|
104
|
+
following symbolic links where Node exposes that facility, file contents and
|
|
105
|
+
directory entries are revalidated on reads and replays, and temporary staging
|
|
106
|
+
directories are removed after success or failure.
|
|
107
|
+
|
|
108
|
+
The configured directory is an operator-controlled local trust boundary. This
|
|
109
|
+
store does not provide remote replication, activation, lifecycle state, or
|
|
110
|
+
protection from an administrator modifying its files. `read(releaseIdentity)`
|
|
111
|
+
returns only a completely revalidated stored submission.
|
|
112
|
+
|
|
113
|
+
## Activation registry
|
|
114
|
+
|
|
115
|
+
`createFileSystemDeploymentActivationRegistry` adds explicit target activation
|
|
116
|
+
without changing accepted release or bundle bytes. The registry requires a
|
|
117
|
+
release reader that completely revalidates an accepted release and its closed
|
|
118
|
+
bundle before returning it; the filesystem submission store satisfies that
|
|
119
|
+
contract directly.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import {
|
|
123
|
+
createFileSystemDeploymentActivationRegistry,
|
|
124
|
+
createFileSystemDeploymentSubmissionStore,
|
|
125
|
+
} from '@hypequery/deployment';
|
|
126
|
+
|
|
127
|
+
const directory = '/var/lib/hypequery/deployments';
|
|
128
|
+
const releases = createFileSystemDeploymentSubmissionStore({ directory });
|
|
129
|
+
const activations = createFileSystemDeploymentActivationRegistry({
|
|
130
|
+
directory,
|
|
131
|
+
releases,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const target = { project: 'analytics', environment: 'production' };
|
|
135
|
+
const current = await activations.current(target);
|
|
136
|
+
const result = await activations.activate({
|
|
137
|
+
target,
|
|
138
|
+
releaseIdentity,
|
|
139
|
+
expectedRevision: current?.revision ?? null,
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`expectedRevision` is a compare-and-swap precondition. A different active
|
|
144
|
+
revision returns `conflict`; requesting the release that is already active
|
|
145
|
+
returns `already-active`. Activating an older accepted release performs a
|
|
146
|
+
rollback through the same operation and produces a new revision, so stale
|
|
147
|
+
pre-rollback callers cannot pass the comparison after an ABA sequence.
|
|
148
|
+
|
|
149
|
+
The filesystem implementation stores an immutable, domain-separated activation
|
|
150
|
+
record for every transition and derives the current release by verifying the
|
|
151
|
+
append-only chain. It has no mutable pointer file or persistent lock to become
|
|
152
|
+
stale after a crash. Activation does not load runtime code, route traffic,
|
|
153
|
+
perform health checks, or authorize callers; those remain provider concerns.
|
|
154
|
+
|
|
155
|
+
## HTTP control plane
|
|
156
|
+
|
|
157
|
+
`createDeploymentControlPlane` combines intake and activation behind closed v1
|
|
158
|
+
routes. Activation reads and writes use a separate target-scoped authorizer;
|
|
159
|
+
write authorization completes before the small JSON request body is consumed.
|
|
160
|
+
The Fetch and Node adapters preserve streaming multipart submission bodies.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import {
|
|
164
|
+
createDeploymentControlPlane,
|
|
165
|
+
createDeploymentControlPlaneNodeHandler,
|
|
166
|
+
} from '@hypequery/deployment';
|
|
167
|
+
|
|
168
|
+
const controlPlane = createDeploymentControlPlane({
|
|
169
|
+
intake,
|
|
170
|
+
activations,
|
|
171
|
+
authenticator,
|
|
172
|
+
authorizer: {
|
|
173
|
+
async authorize({ principal, action, target }) {
|
|
174
|
+
return canControlDeployment(principal, action, target);
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const nodeHandler = createDeploymentControlPlaneNodeHandler(controlPlane);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The control plane exposes release submission, compare-and-swap activation,
|
|
183
|
+
current state, and bounded cursor history. It returns stable, bounded JSON error
|
|
184
|
+
codes and suppresses internal provider and filesystem details. The HTTP
|
|
185
|
+
contract is specified in `specs/deployment/0003-control-plane-http.md`.
|
|
186
|
+
|
|
187
|
+
## Runtime materialization
|
|
188
|
+
|
|
189
|
+
`createDeploymentRuntimeMaterializer` converts the current target activation
|
|
190
|
+
into a private runtime snapshot. It revalidates the accepted release and closed
|
|
191
|
+
bundle, copies and hashes each runtime artifact without following symbolic
|
|
192
|
+
links, and confirms the activation revision again before returning.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { createDeploymentRuntimeMaterializer } from '@hypequery/deployment';
|
|
196
|
+
|
|
197
|
+
const materializer = createDeploymentRuntimeMaterializer({
|
|
198
|
+
activations,
|
|
199
|
+
releases: store,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const snapshot = await materializer.current({
|
|
203
|
+
project: 'analytics',
|
|
204
|
+
environment: 'production',
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Artifact `read()` methods return fresh byte copies, so neither callers nor later
|
|
209
|
+
changes to durable storage can alter a materialized snapshot. Runtime imports,
|
|
210
|
+
process lifecycle, readiness, and traffic switching remain separate concerns.
|
|
211
|
+
|
|
212
|
+
## Runtime supervision
|
|
213
|
+
|
|
214
|
+
`createDeploymentRuntimeSupervisor` starts materialized snapshots through a
|
|
215
|
+
runtime factory, checks candidate readiness, confirms activation again, and
|
|
216
|
+
atomically changes the generation used for new named-query invocations. Failed
|
|
217
|
+
or superseded candidates never displace a healthy generation.
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import {
|
|
221
|
+
createDeploymentRuntimeSupervisor,
|
|
222
|
+
createNodeWorkerDeploymentRuntimeFactory,
|
|
223
|
+
} from '@hypequery/deployment';
|
|
224
|
+
|
|
225
|
+
const supervisor = createDeploymentRuntimeSupervisor({
|
|
226
|
+
materializer,
|
|
227
|
+
factory: createNodeWorkerDeploymentRuntimeFactory(),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await supervisor.reconcile({ project: 'analytics', environment: 'production' });
|
|
231
|
+
const result = await supervisor.invoke({
|
|
232
|
+
target: { project: 'analytics', environment: 'production' },
|
|
233
|
+
query: 'orders',
|
|
234
|
+
argument: { input, ctx: { tenantId } },
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Calls already assigned to an old generation may finish after cutover. That
|
|
239
|
+
generation receives no new calls and closes when its in-flight work reaches
|
|
240
|
+
zero or the drain deadline expires. The reference Node factory loads exact
|
|
241
|
+
materialized bytes in worker threads and removes their temporary files on
|
|
242
|
+
shutdown. Provider factories can implement Python, process, container, or
|
|
243
|
+
remote-sandbox isolation behind the same lifecycle interface.
|
|
244
|
+
|
|
245
|
+
The reference worker is a lifecycle boundary, not a hostile-code security
|
|
246
|
+
sandbox. Only trusted deployment code should use it directly.
|
|
247
|
+
|
|
68
248
|
The package is ESM-only and requires Node.js 20 or newer.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ProtocolDeploymentReleaseEnvelope, type ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
|
|
2
|
+
export type DeploymentActivationErrorCode = 'HQ_DEPLOYMENT_ACTIVATION_CONFIGURATION' | 'HQ_DEPLOYMENT_ACTIVATION_INVALID_REQUEST' | 'HQ_DEPLOYMENT_ACTIVATION_RELEASE_NOT_FOUND' | 'HQ_DEPLOYMENT_ACTIVATION_RELEASE_UNAVAILABLE' | 'HQ_DEPLOYMENT_ACTIVATION_TARGET_MISMATCH' | 'HQ_DEPLOYMENT_ACTIVATION_CORRUPT_STATE' | 'HQ_DEPLOYMENT_ACTIVATION_IO';
|
|
3
|
+
export declare class DeploymentActivationError extends Error {
|
|
4
|
+
readonly code: DeploymentActivationErrorCode;
|
|
5
|
+
constructor(code: DeploymentActivationErrorCode, message: string, options?: {
|
|
6
|
+
readonly cause?: unknown;
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export interface DeploymentActivationRecord {
|
|
10
|
+
readonly kind: 'hypequery-deployment-activation';
|
|
11
|
+
readonly version: 1;
|
|
12
|
+
readonly revision: string;
|
|
13
|
+
readonly target: ProtocolDeploymentReleaseTarget;
|
|
14
|
+
readonly releaseIdentity: string;
|
|
15
|
+
readonly previousRevision: string | null;
|
|
16
|
+
readonly previousReleaseIdentity: string | null;
|
|
17
|
+
readonly activatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
export interface DeploymentActivationRequest {
|
|
20
|
+
readonly target: ProtocolDeploymentReleaseTarget;
|
|
21
|
+
readonly releaseIdentity: string;
|
|
22
|
+
/** `null` means the caller expects this target to have no active release. */
|
|
23
|
+
readonly expectedRevision: string | null;
|
|
24
|
+
}
|
|
25
|
+
export type DeploymentActivationResult = {
|
|
26
|
+
readonly status: 'activated' | 'already-active';
|
|
27
|
+
readonly activation: DeploymentActivationRecord;
|
|
28
|
+
} | {
|
|
29
|
+
readonly status: 'conflict';
|
|
30
|
+
readonly current: DeploymentActivationRecord | null;
|
|
31
|
+
};
|
|
32
|
+
export interface DeploymentActivationHistoryQuery {
|
|
33
|
+
readonly limit: number;
|
|
34
|
+
readonly before?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface DeploymentActivationHistoryPage {
|
|
37
|
+
readonly activations: readonly DeploymentActivationRecord[];
|
|
38
|
+
readonly nextBefore: string | null;
|
|
39
|
+
}
|
|
40
|
+
export interface DeploymentActivationRelease {
|
|
41
|
+
readonly release: ProtocolDeploymentReleaseEnvelope;
|
|
42
|
+
readonly releaseIdentity: string;
|
|
43
|
+
}
|
|
44
|
+
export interface DeploymentReleaseReader {
|
|
45
|
+
/** Return only accepted releases whose release and closed bundle remain completely valid. */
|
|
46
|
+
read(releaseIdentity: string): Promise<DeploymentActivationRelease | undefined>;
|
|
47
|
+
}
|
|
48
|
+
export interface FileSystemDeploymentActivationRegistryOptions {
|
|
49
|
+
readonly directory: string;
|
|
50
|
+
readonly releases: DeploymentReleaseReader;
|
|
51
|
+
readonly clock?: () => Date;
|
|
52
|
+
}
|
|
53
|
+
export interface DeploymentActivationRegistry {
|
|
54
|
+
activate(request: DeploymentActivationRequest): Promise<DeploymentActivationResult>;
|
|
55
|
+
current(target: ProtocolDeploymentReleaseTarget): Promise<DeploymentActivationRecord | undefined>;
|
|
56
|
+
history(target: ProtocolDeploymentReleaseTarget): Promise<readonly DeploymentActivationRecord[]>;
|
|
57
|
+
historyPage(target: ProtocolDeploymentReleaseTarget, query: DeploymentActivationHistoryQuery): Promise<DeploymentActivationHistoryPage>;
|
|
58
|
+
}
|
|
59
|
+
/** Validate and recompute an immutable deployment activation record. */
|
|
60
|
+
export declare function validateDeploymentActivationRecord(input: unknown): DeploymentActivationRecord;
|
|
61
|
+
export declare function createFileSystemDeploymentActivationRegistry(options: FileSystemDeploymentActivationRegistryOptions): DeploymentActivationRegistry;
|
|
62
|
+
//# sourceMappingURL=activation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activation.d.ts","sourceRoot":"","sources":["../src/activation.ts"],"names":[],"mappings":"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"}
|