@appforge-ci/core 0.2.3 → 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/dist/agent-readiness.d.ts +232 -1
- package/dist/agent-readiness.d.ts.map +1 -1
- package/dist/agent-readiness.js +294 -1
- package/dist/agent-readiness.js.map +1 -1
- package/dist/agent-readiness.test.js +253 -19
- package/dist/agent-readiness.test.js.map +1 -1
- package/dist/api-client.d.ts +42 -3
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +27 -0
- package/dist/api-client.js.map +1 -1
- package/dist/api-client.test.js +22 -0
- package/dist/api-client.test.js.map +1 -1
- package/dist/attestation.d.ts +265 -0
- package/dist/attestation.d.ts.map +1 -0
- package/dist/attestation.js +327 -0
- package/dist/attestation.js.map +1 -0
- package/dist/attestation.test.d.ts +2 -0
- package/dist/attestation.test.d.ts.map +1 -0
- package/dist/attestation.test.js +218 -0
- package/dist/attestation.test.js.map +1 -0
- package/dist/build-vpn.d.ts +10 -0
- package/dist/build-vpn.d.ts.map +1 -0
- package/dist/build-vpn.js +23 -0
- package/dist/build-vpn.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +342 -7
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +92 -2
- package/dist/schemas.js.map +1 -1
- package/dist/types.d.ts +149 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vpn-egress-presets.d.ts +142 -0
- package/dist/vpn-egress-presets.d.ts.map +1 -0
- package/dist/vpn-egress-presets.js +318 -0
- package/dist/vpn-egress-presets.js.map +1 -0
- package/dist/vpn-egress-presets.test.d.ts +2 -0
- package/dist/vpn-egress-presets.test.d.ts.map +1 -0
- package/dist/vpn-egress-presets.test.js +210 -0
- package/dist/vpn-egress-presets.test.js.map +1 -0
- package/dist/vpn-egress.d.ts +57 -0
- package/dist/vpn-egress.d.ts.map +1 -0
- package/dist/vpn-egress.js +126 -0
- package/dist/vpn-egress.js.map +1 -0
- package/dist/vpn-egress.test.d.ts +2 -0
- package/dist/vpn-egress.test.d.ts.map +1 -0
- package/dist/vpn-egress.test.js +133 -0
- package/dist/vpn-egress.test.js.map +1 -0
- package/dist/vpn-ssh.d.ts +45 -0
- package/dist/vpn-ssh.d.ts.map +1 -0
- package/dist/vpn-ssh.js +108 -0
- package/dist/vpn-ssh.js.map +1 -0
- package/dist/vpn-ssh.test.d.ts +2 -0
- package/dist/vpn-ssh.test.d.ts.map +1 -0
- package/dist/vpn-ssh.test.js +75 -0
- package/dist/vpn-ssh.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -98,6 +98,175 @@ export declare function agentFitsPlatform(readiness: AgentReadiness, platform: P
|
|
|
98
98
|
* getting bound somewhere it can't actually run.
|
|
99
99
|
*/
|
|
100
100
|
export declare function agentCanBuildPlatform(agent: Parameters<typeof computeAgentReadiness>[0], platform: Platform, now?: Date): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Build-isolation level an agent reports (`Agent.enforcementLevel`,
|
|
103
|
+
* migrations/0043). Ordered: a higher level satisfies a lower requirement.
|
|
104
|
+
* - `none` no kernel-enforced isolation of build processes (today).
|
|
105
|
+
* - `sandbox` builds run under a kernel sandbox profile (Seatbelt).
|
|
106
|
+
* - `sandbox_uid` ... and as a dedicated unprivileged user.
|
|
107
|
+
* Anything unrecognised ranks as `none`: an unknown level never satisfies a
|
|
108
|
+
* requirement (fail closed).
|
|
109
|
+
*/
|
|
110
|
+
export declare const ENFORCEMENT_LEVELS: readonly ["none", "sandbox", "sandbox_uid"];
|
|
111
|
+
export type EnforcementLevel = (typeof ENFORCEMENT_LEVELS)[number];
|
|
112
|
+
export declare function parseEnforcementLevel(raw: unknown): EnforcementLevel;
|
|
113
|
+
/** 0 (`none`) .. 2 (`sandbox_uid`); unknown input is 0. */
|
|
114
|
+
export declare function enforcementLevelRank(level: unknown): number;
|
|
115
|
+
/** What a feature id may look like (`Agent.vpnFeatures` entries, tunnel
|
|
116
|
+
* `requires` entries): lowercase, short, no whitespace or separators that
|
|
117
|
+
* could be abused in a log line or SQL JSON. Ids carry their own version
|
|
118
|
+
* suffix (`ssh-v1`) so a breaking change is a NEW id, never a redefinition. */
|
|
119
|
+
export declare const VPN_FEATURE_ID_PATTERN: RegExp;
|
|
120
|
+
/** An agent reports at most this many features; the rest are dropped. */
|
|
121
|
+
export declare const MAX_VPN_FEATURES = 32;
|
|
122
|
+
/**
|
|
123
|
+
* Feature ids the regulated-VPN program will introduce. Informational: the
|
|
124
|
+
* scheduler's superset check works on whatever ids a tunnel requires and an
|
|
125
|
+
* agent reports, so a new id needs no change here. Only `egress-policy-v1` is
|
|
126
|
+
* required (by a strict-egress tunnel) and reported (by an agent with a new
|
|
127
|
+
* enough verified helper) so far; the others land in later PRs.
|
|
128
|
+
*/
|
|
129
|
+
export declare const KNOWN_VPN_FEATURES: readonly ["egress-policy-v1", "ssh-v1", "sandbox-v1", "evidence-v1"];
|
|
130
|
+
/** Prefix of a requirement token that asks for a minimum enforcement level
|
|
131
|
+
* (`enforcement:sandbox`) rather than a feature id. */
|
|
132
|
+
export declare const ENFORCEMENT_REQUIREMENT_PREFIX = "enforcement:";
|
|
133
|
+
/**
|
|
134
|
+
* The canonical form of a reported feature list: only well-formed ids,
|
|
135
|
+
* de-duplicated, sorted, capped at `MAX_VPN_FEATURES`. Anything else is
|
|
136
|
+
* dropped, never repaired. This is the ONLY form stored in
|
|
137
|
+
* `Agent.vpnFeatures`, so the scheduler's SQL superset check can compare
|
|
138
|
+
* strings exactly.
|
|
139
|
+
*/
|
|
140
|
+
export declare function canonicalVpnFeatures(input: readonly unknown[] | null | undefined): string[];
|
|
141
|
+
/** Parses the `Agent.vpnFeatures` JSON column. Malformed, non-array or null
|
|
142
|
+
* input is `[]` (no features), never an exception. */
|
|
143
|
+
export declare function parseVpnFeatures(raw: string | null | undefined): string[];
|
|
144
|
+
/** What a tunnel needs from the Mac that serves its builds. */
|
|
145
|
+
export interface VpnRequirements {
|
|
146
|
+
/** Feature ids the agent must report (a superset check). */
|
|
147
|
+
features: readonly string[];
|
|
148
|
+
/** Minimum `enforcementLevel`. */
|
|
149
|
+
minEnforcementLevel: EnforcementLevel;
|
|
150
|
+
}
|
|
151
|
+
export declare const NO_VPN_REQUIREMENTS: VpnRequirements;
|
|
152
|
+
/** The feature id of the strict egress policy: the helper enforces a job's
|
|
153
|
+
* `egress` allow-list and echoes its hash, and the agent verifies the echo. A
|
|
154
|
+
* tunnel with `strictEgress` requires it (see `vpnTunnelRequirements`). */
|
|
155
|
+
export declare const EGRESS_POLICY_FEATURE = "egress-policy-v1";
|
|
156
|
+
/**
|
|
157
|
+
* The slice of a tunnel `vpnTunnelRequirements` reads: the status, plus the two
|
|
158
|
+
* settings that make a tunnel demand something of the Mac (migrations/0045).
|
|
159
|
+
* The columns are required (not optional) so a caller that builds this from a
|
|
160
|
+
* query cannot forget them: a forgotten setting would silently mean "nothing
|
|
161
|
+
* required".
|
|
162
|
+
*/
|
|
163
|
+
export interface VpnTunnelRequirementSource {
|
|
164
|
+
status: "pending" | "ready" | "degraded" | "disabled" | null;
|
|
165
|
+
/** `VpnTunnel.strictEgress` (0/1). */
|
|
166
|
+
strictEgress: number | boolean | null;
|
|
167
|
+
/** `VpnTunnel.complianceMode` ('standard' | 'regulated'). */
|
|
168
|
+
complianceMode: string | null;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* What the given tunnel requires of an agent. This is the single place a tunnel
|
|
172
|
+
* setting makes a requirement; the scheduler, the guarded claim SQL, the lease
|
|
173
|
+
* response (`job.requires`) and the agent's abort check all consume this one
|
|
174
|
+
* answer.
|
|
175
|
+
*
|
|
176
|
+
* - `strictEgress`: the Mac must report `egress-policy-v1`, because an agent or
|
|
177
|
+
* helper that predates the policy ignores the `egress` block and would run
|
|
178
|
+
* the build with no allow-list. Only an exact `0`/`false` means "not strict":
|
|
179
|
+
* a missing, null or unrecognised value requires it (fail closed).
|
|
180
|
+
* - `complianceMode` other than exactly `standard` (regulated, or a value this
|
|
181
|
+
* code does not know): strict egress as above, and at least `sandbox`
|
|
182
|
+
* enforcement. No Mac reports a sandbox yet, so a regulated tunnel's builds
|
|
183
|
+
* wait for one rather than run unconfined.
|
|
184
|
+
*
|
|
185
|
+
* A standard-mode tunnel with strict egress off requires nothing, exactly as
|
|
186
|
+
* before, so the server can ship ahead of any agent.
|
|
187
|
+
*/
|
|
188
|
+
export declare function vpnTunnelRequirements(tunnel: VpnTunnelRequirementSource): VpnRequirements;
|
|
189
|
+
/** The wire form of requirements, carried in the lease response as
|
|
190
|
+
* `job.requires`: feature ids plus `enforcement:<level>` for a level above
|
|
191
|
+
* `none`. An agent aborts a job with any token it cannot satisfy. */
|
|
192
|
+
export declare function vpnRequirementTokens(req: VpnRequirements): string[];
|
|
193
|
+
/**
|
|
194
|
+
* Which of `tokens` the capabilities do NOT satisfy. A token is satisfied only
|
|
195
|
+
* if it is a feature id the capabilities list, or `enforcement:<level>` with a
|
|
196
|
+
* KNOWN level the capabilities reach. An unknown token, an unknown level, or a
|
|
197
|
+
* non-string entry is unmet: this is what makes the check fail closed against a
|
|
198
|
+
* requirement introduced by a newer control plane than the agent. A non-array
|
|
199
|
+
* `tokens` is itself unmet (reported as one entry).
|
|
200
|
+
*
|
|
201
|
+
* Used by both the scheduler (`agentSatisfiesVpnRequirements`) and the agent
|
|
202
|
+
* (before it starts a leased VPN job), so they cannot disagree.
|
|
203
|
+
*/
|
|
204
|
+
export declare function unmetRequirements(tokens: unknown, capabilities: {
|
|
205
|
+
features: readonly string[];
|
|
206
|
+
enforcementLevel: unknown;
|
|
207
|
+
}): string[];
|
|
208
|
+
/**
|
|
209
|
+
* Operator taint (`Agent.dedicatedOrgId`): a Mac dedicated to an org takes only
|
|
210
|
+
* that org's builds, VPN or not. `null` = shared. An empty string is NOT shared:
|
|
211
|
+
* only an explicit null is (a mangled value must not widen who may build here).
|
|
212
|
+
*/
|
|
213
|
+
export declare function agentAllowsOrg(dedicatedOrgId: string | null, buildOrgId: string): boolean;
|
|
214
|
+
/**
|
|
215
|
+
* Where a tunnel's builds may run (`VpnTunnel.hostPolicy`, migrations/0047).
|
|
216
|
+
* - `shared` any approved, VPN-capable Mac, under the exclusive-host lock
|
|
217
|
+
* (today's behaviour, and the default).
|
|
218
|
+
* - `pinned` only Macs the operator pinned to the tunnel (node affinity).
|
|
219
|
+
* - `dedicated` pinned, AND the Mac is dedicated to this org
|
|
220
|
+
* (`Agent.dedicatedOrgId`, so no other org's work is leased to
|
|
221
|
+
* it), AND it reports no co-hosted GitHub Actions runner.
|
|
222
|
+
* Set only by a platform admin; org admins can read it, never write it.
|
|
223
|
+
*/
|
|
224
|
+
export declare const VPN_HOST_POLICIES: readonly ["shared", "pinned", "dedicated"];
|
|
225
|
+
export type VpnHostPolicy = (typeof VPN_HOST_POLICIES)[number];
|
|
226
|
+
/**
|
|
227
|
+
* What a customer sees for their tunnel's host policy (read-only; the operator
|
|
228
|
+
* sets it). Deliberately plain about the limits: "dedicated" is a scheduling
|
|
229
|
+
* guarantee about which machines AppForge runs this org's VPN builds on, not a
|
|
230
|
+
* claim of hardware isolation or of any compliance certification.
|
|
231
|
+
*/
|
|
232
|
+
export declare function describeVpnHostPolicy(policy: VpnHostPolicy | string, pinnedHostCount?: number): string;
|
|
233
|
+
/** How long a queued VPN build of a `pinned`/`dedicated` tunnel waits for a ready
|
|
234
|
+
* pinned Mac before the control plane's sweep fails it as an infra failure with a
|
|
235
|
+
* refund. The wait is measured as CONTINUOUS host unavailability, not build age:
|
|
236
|
+
* the sweep records when the tunnel's pinned Macs stopped being ready
|
|
237
|
+
* (`VpnTunnelHostWait`) and fails a build only once they have all been unready
|
|
238
|
+
* for this long AND the build itself has been queued this long. Any tick that
|
|
239
|
+
* finds a ready Mac (busy counts as ready) restarts the clock, so a Mac that is
|
|
240
|
+
* briefly away (a reboot or self-update cycle, a short drain) does not fail a
|
|
241
|
+
* build that has merely been waiting in line. Longer than the 120 s
|
|
242
|
+
* lock-reservation window. The sweep runs every 5 minutes, so the effective
|
|
243
|
+
* wait is this plus up to one cron interval. */
|
|
244
|
+
export declare const VPN_HOST_WAIT_SECONDS = 600;
|
|
245
|
+
/** If the sweep's last observation of a tunnel's pinned hosts is older than this
|
|
246
|
+
* (three cron ticks), it knows nothing about the gap, so the next "unready"
|
|
247
|
+
* observation starts a fresh unavailability stretch instead of extending an old one. */
|
|
248
|
+
export declare const VPN_HOST_OBSERVATION_GAP_SECONDS = 900;
|
|
249
|
+
/**
|
|
250
|
+
* Parses a stored policy. An UNKNOWN value is `dedicated`, the strictest, not
|
|
251
|
+
* `shared`: a mangled policy must tighten placement, never loosen it (the
|
|
252
|
+
* column's CHECK makes this unreachable today; it is here so a future value
|
|
253
|
+
* read by an older Worker fails closed).
|
|
254
|
+
*/
|
|
255
|
+
export declare function parseVpnHostPolicy(raw: unknown): VpnHostPolicy;
|
|
256
|
+
/**
|
|
257
|
+
* Whether an agent may serve a VPN build under the tunnel's host policy.
|
|
258
|
+
* - `hostPinned` is "this Mac is in the tunnel's pin set".
|
|
259
|
+
* - `dedicatedOrgId` is the Mac's operator taint.
|
|
260
|
+
* - `coHostedRunner` is what its last (fresh) heartbeat reported: only an
|
|
261
|
+
* exact 0 means "no GitHub Actions runner on the machine". 1, null
|
|
262
|
+
* (unreported) and anything else all refuse for `dedicated`.
|
|
263
|
+
* `shared` always passes (and is the only case that needs no pin).
|
|
264
|
+
*/
|
|
265
|
+
export declare function agentSatisfiesHostPolicy(policy: VpnHostPolicy | string, orgId: string, agent: {
|
|
266
|
+
hostPinned: boolean;
|
|
267
|
+
dedicatedOrgId: string | null;
|
|
268
|
+
coHostedRunner: number | null;
|
|
269
|
+
}): boolean;
|
|
101
270
|
/**
|
|
102
271
|
* The VPN-related slice of an agent row the scheduler needs — kept as its
|
|
103
272
|
* own structural type (not added to `AgentInfo`) because it comes straight
|
|
@@ -112,6 +281,21 @@ export interface VpnSchedulingAgent {
|
|
|
112
281
|
vpnApproved: number;
|
|
113
282
|
/** The org this Mac is exclusively locked to, or null when unlocked. */
|
|
114
283
|
vpnLockOrgId: string | null;
|
|
284
|
+
/** Features the agent proved it can enforce — the caller passes `[]` unless
|
|
285
|
+
* the heartbeat that reported them is fresh (see `agentProvesVpnEnforcement`). */
|
|
286
|
+
vpnFeatures: readonly string[];
|
|
287
|
+
/** Same provenance and freshness rule as `vpnFeatures`. */
|
|
288
|
+
enforcementLevel: EnforcementLevel;
|
|
289
|
+
/** Operator-set taint (migrations/0043): the one org this Mac may serve, or
|
|
290
|
+
* null for a shared Mac. Required (not optional) so a caller can never
|
|
291
|
+
* forget it: forgetting it would silently un-taint a dedicated Mac. */
|
|
292
|
+
dedicatedOrgId: string | null;
|
|
293
|
+
/** What the last heartbeat reported about a co-hosted GitHub Actions runner
|
|
294
|
+
* (migrations/0047): 1 present, 0 absent, null unknown. As with
|
|
295
|
+
* `vpnFeatures`, the caller passes null unless that heartbeat is fresh.
|
|
296
|
+
* Required so a caller cannot forget it (forgetting would let a dedicated
|
|
297
|
+
* policy pass on a stale value). */
|
|
298
|
+
coHostedRunner: number | null;
|
|
115
299
|
}
|
|
116
300
|
/** The slice of a queued-build candidate (`@appforge/db`'s
|
|
117
301
|
* `QueuedBuildCandidate`) the VPN predicate reads. */
|
|
@@ -120,17 +304,34 @@ export interface VpnSchedulingBuild {
|
|
|
120
304
|
useVpn: boolean;
|
|
121
305
|
/** The build's org's tunnel status, or null when it has no tunnel. */
|
|
122
306
|
vpnTunnelStatus: "pending" | "ready" | "degraded" | "disabled" | null;
|
|
307
|
+
/** What the build's tunnel requires of the Mac (`vpnTunnelRequirements`).
|
|
308
|
+
* Ignored for a build that doesn't use the VPN. */
|
|
309
|
+
requirements: VpnRequirements;
|
|
310
|
+
/** The build's tunnel's host policy (`VpnTunnel.hostPolicy`, migrations/0047).
|
|
311
|
+
* Ignored for a build that doesn't use the VPN. */
|
|
312
|
+
hostPolicy: VpnHostPolicy;
|
|
313
|
+
/** Whether the asking agent is pinned to the build's tunnel
|
|
314
|
+
* (`VpnTunnelHostPin`). Required: forgetting it must read as "not pinned". */
|
|
315
|
+
agentPinned: boolean;
|
|
123
316
|
}
|
|
124
317
|
/**
|
|
125
318
|
* Scheduler predicate for the site-to-site VPN's exclusive-host lock —
|
|
126
319
|
* the "taint/toleration" check, ANDed alongside `agentCanBuildPlatform` in
|
|
127
320
|
* `/agents/lease-job` before any `claimBuildById` attempt:
|
|
128
321
|
*
|
|
322
|
+
* - A Mac dedicated to an org (`dedicatedOrgId`) refuses every other org's
|
|
323
|
+
* builds, VPN or not.
|
|
129
324
|
* - A build that doesn't use the VPN is never offered to a Mac that is
|
|
130
325
|
* locked to an org (the lock means "only that org's VPN builds here").
|
|
131
326
|
* - A VPN build needs a VPN-capable AND admin-approved Mac (issue #643), a tunnel that exists and isn't
|
|
132
327
|
* `disabled`, and a Mac that is either unlocked or already locked to
|
|
133
328
|
* this same org (same-org VPN builds share the lock).
|
|
329
|
+
* - A VPN build also needs a Mac that satisfies the tunnel's requirements:
|
|
330
|
+
* it reports every required feature and a high enough enforcement level
|
|
331
|
+
* (capability negotiation, migrations/0043). Nothing is required today.
|
|
332
|
+
* - A VPN build whose tunnel is `pinned` or `dedicated` (migrations/0047) only
|
|
333
|
+
* goes to a Mac pinned to that tunnel, and `dedicated` additionally needs the
|
|
334
|
+
* Mac dedicated to the org with no co-hosted GitHub Actions runner.
|
|
134
335
|
*
|
|
135
336
|
* This is an optimization, not the safety net: it filters on the agent
|
|
136
337
|
* row loaded at the start of the request, which can be stale by the time
|
|
@@ -141,6 +342,36 @@ export interface VpnSchedulingBuild {
|
|
|
141
342
|
* proves it works.
|
|
142
343
|
*/
|
|
143
344
|
export declare function agentSatisfiesVpnRequirements(agent: VpnSchedulingAgent, build: VpnSchedulingBuild): boolean;
|
|
345
|
+
/** The first agent release that can enforce a VPN job (it ships the signed
|
|
346
|
+
* WireGuard helper and honours `job.vpn`). Anything older IGNORES `job.vpn`
|
|
347
|
+
* and would run the build with no tunnel, so it must never be leased one,
|
|
348
|
+
* whatever its (possibly stale) `Agent.vpnCapable` row says. */
|
|
349
|
+
export declare const MIN_VPN_AGENT_VERSION = "0.2.0";
|
|
350
|
+
/** How recent `Agent.lastSeenAt` must be for `vpnCapable` to count: the flag
|
|
351
|
+
* is only as good as the heartbeat that last set it. Three missed 15s beats
|
|
352
|
+
* plus slack — and well under the 60s-ish scheduler cadence's tolerance. */
|
|
353
|
+
export declare const VPN_CAPABILITY_MAX_AGE_SECONDS = 120;
|
|
354
|
+
/**
|
|
355
|
+
* Numeric `major.minor.patch` comparison (a `-prerelease`/`+build` suffix is
|
|
356
|
+
* ignored). Returns <0, 0, >0, or `null` when either side isn't a plain
|
|
357
|
+
* semver — callers treat `null` as "unknown", never as "new enough".
|
|
358
|
+
*/
|
|
359
|
+
export declare function compareSemver(a: string, b: string): number | null;
|
|
360
|
+
/** True only for a known version >= `MIN_VPN_AGENT_VERSION`. A missing or
|
|
361
|
+
* unparseable version is NOT capable (fail closed). */
|
|
362
|
+
export declare function agentVersionSupportsVpn(agentVersion: string | null | undefined): boolean;
|
|
363
|
+
/**
|
|
364
|
+
* Server-side proof that the leasing agent can actually enforce a VPN job,
|
|
365
|
+
* independent of the sticky-ish `vpnCapable` bit: the agent's reported
|
|
366
|
+
* version must be >= `MIN_VPN_AGENT_VERSION` and its last heartbeat (which is
|
|
367
|
+
* what (re-)asserts `vpnCapable`) must be recent. ANDed with
|
|
368
|
+
* `agentSatisfiesVpnRequirements` in `/agents/lease-job`; the guarded SQL in
|
|
369
|
+
* `claimBuildById`/`acquireVpnLock` re-checks freshness and pins the version.
|
|
370
|
+
*/
|
|
371
|
+
export declare function agentProvesVpnEnforcement(agent: {
|
|
372
|
+
agentVersion: string | null;
|
|
373
|
+
lastSeenAt: string;
|
|
374
|
+
}, now?: Date): boolean;
|
|
144
375
|
/**
|
|
145
376
|
* The currently published packages/agent build — bump this in lockstep
|
|
146
377
|
* with packages/agent/package.json's own "version" field (and
|
|
@@ -154,7 +385,7 @@ export declare function agentSatisfiesVpnRequirements(agent: VpnSchedulingAgent,
|
|
|
154
385
|
* version against this same constant to decide whether to auto-issue an
|
|
155
386
|
* `UPDATE_AGENT` command (Phase 2's remote command, reused unchanged).
|
|
156
387
|
*/
|
|
157
|
-
export declare const CURRENT_AGENT_VERSION = "0.
|
|
388
|
+
export declare const CURRENT_AGENT_VERSION = "0.3.0";
|
|
158
389
|
/**
|
|
159
390
|
* Whether a fleet member is running the latest known agent build.
|
|
160
391
|
* `null` — deliberately not "outdated" — for a row with no reported
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-readiness.d.ts","sourceRoot":"","sources":["../src/agent-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB;;;sBAGkB;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB;;;oCAGgC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAE3C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEhD;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAEhF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,GAAG,YAAY,CAAC,EAC/C,GAAG,GAAE,IAAiB,GACrB,SAAS,CAAC,QAAQ,CAAC,CAErB;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,IAAI,CACT,SAAS,EACT,cAAc,GAAG,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,GAAG,gBAAgB,GAAG,QAAQ,GAAG,YAAY,CACvH,EACD,GAAG,GAAE,IAAiB,GACrB,cAAc,CAUhB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAYxF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC,CAAC,CAAC,EAClD,QAAQ,EAAE,QAAQ,EAClB,GAAG,GAAE,IAAiB,GACrB,OAAO,CAET;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB;+EAC2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-readiness.d.ts","sourceRoot":"","sources":["../src/agent-readiness.ts"],"names":[],"mappings":"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"}
|
package/dist/agent-readiness.js
CHANGED
|
@@ -100,16 +100,247 @@ export function agentFitsPlatform(readiness, platform) {
|
|
|
100
100
|
export function agentCanBuildPlatform(agent, platform, now = new Date()) {
|
|
101
101
|
return agentFitsPlatform(computeAgentReadiness(agent, now), platform);
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Build-isolation level an agent reports (`Agent.enforcementLevel`,
|
|
105
|
+
* migrations/0043). Ordered: a higher level satisfies a lower requirement.
|
|
106
|
+
* - `none` no kernel-enforced isolation of build processes (today).
|
|
107
|
+
* - `sandbox` builds run under a kernel sandbox profile (Seatbelt).
|
|
108
|
+
* - `sandbox_uid` ... and as a dedicated unprivileged user.
|
|
109
|
+
* Anything unrecognised ranks as `none`: an unknown level never satisfies a
|
|
110
|
+
* requirement (fail closed).
|
|
111
|
+
*/
|
|
112
|
+
export const ENFORCEMENT_LEVELS = ["none", "sandbox", "sandbox_uid"];
|
|
113
|
+
export function parseEnforcementLevel(raw) {
|
|
114
|
+
return typeof raw === "string" && ENFORCEMENT_LEVELS.includes(raw) ? raw : "none";
|
|
115
|
+
}
|
|
116
|
+
/** 0 (`none`) .. 2 (`sandbox_uid`); unknown input is 0. */
|
|
117
|
+
export function enforcementLevelRank(level) {
|
|
118
|
+
return ENFORCEMENT_LEVELS.indexOf(parseEnforcementLevel(level));
|
|
119
|
+
}
|
|
120
|
+
/** What a feature id may look like (`Agent.vpnFeatures` entries, tunnel
|
|
121
|
+
* `requires` entries): lowercase, short, no whitespace or separators that
|
|
122
|
+
* could be abused in a log line or SQL JSON. Ids carry their own version
|
|
123
|
+
* suffix (`ssh-v1`) so a breaking change is a NEW id, never a redefinition. */
|
|
124
|
+
export const VPN_FEATURE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
125
|
+
/** An agent reports at most this many features; the rest are dropped. */
|
|
126
|
+
export const MAX_VPN_FEATURES = 32;
|
|
127
|
+
/**
|
|
128
|
+
* Feature ids the regulated-VPN program will introduce. Informational: the
|
|
129
|
+
* scheduler's superset check works on whatever ids a tunnel requires and an
|
|
130
|
+
* agent reports, so a new id needs no change here. Only `egress-policy-v1` is
|
|
131
|
+
* required (by a strict-egress tunnel) and reported (by an agent with a new
|
|
132
|
+
* enough verified helper) so far; the others land in later PRs.
|
|
133
|
+
*/
|
|
134
|
+
export const KNOWN_VPN_FEATURES = ["egress-policy-v1", "ssh-v1", "sandbox-v1", "evidence-v1"];
|
|
135
|
+
/** Prefix of a requirement token that asks for a minimum enforcement level
|
|
136
|
+
* (`enforcement:sandbox`) rather than a feature id. */
|
|
137
|
+
export const ENFORCEMENT_REQUIREMENT_PREFIX = "enforcement:";
|
|
138
|
+
/**
|
|
139
|
+
* The canonical form of a reported feature list: only well-formed ids,
|
|
140
|
+
* de-duplicated, sorted, capped at `MAX_VPN_FEATURES`. Anything else is
|
|
141
|
+
* dropped, never repaired. This is the ONLY form stored in
|
|
142
|
+
* `Agent.vpnFeatures`, so the scheduler's SQL superset check can compare
|
|
143
|
+
* strings exactly.
|
|
144
|
+
*/
|
|
145
|
+
export function canonicalVpnFeatures(input) {
|
|
146
|
+
if (!Array.isArray(input))
|
|
147
|
+
return [];
|
|
148
|
+
const ok = new Set();
|
|
149
|
+
for (const v of input)
|
|
150
|
+
if (typeof v === "string" && VPN_FEATURE_ID_PATTERN.test(v))
|
|
151
|
+
ok.add(v);
|
|
152
|
+
return [...ok].sort().slice(0, MAX_VPN_FEATURES);
|
|
153
|
+
}
|
|
154
|
+
/** Parses the `Agent.vpnFeatures` JSON column. Malformed, non-array or null
|
|
155
|
+
* input is `[]` (no features), never an exception. */
|
|
156
|
+
export function parseVpnFeatures(raw) {
|
|
157
|
+
if (!raw)
|
|
158
|
+
return [];
|
|
159
|
+
try {
|
|
160
|
+
return canonicalVpnFeatures(JSON.parse(raw));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export const NO_VPN_REQUIREMENTS = { features: [], minEnforcementLevel: "none" };
|
|
167
|
+
/** The feature id of the strict egress policy: the helper enforces a job's
|
|
168
|
+
* `egress` allow-list and echoes its hash, and the agent verifies the echo. A
|
|
169
|
+
* tunnel with `strictEgress` requires it (see `vpnTunnelRequirements`). */
|
|
170
|
+
export const EGRESS_POLICY_FEATURE = "egress-policy-v1";
|
|
171
|
+
/**
|
|
172
|
+
* What the given tunnel requires of an agent. This is the single place a tunnel
|
|
173
|
+
* setting makes a requirement; the scheduler, the guarded claim SQL, the lease
|
|
174
|
+
* response (`job.requires`) and the agent's abort check all consume this one
|
|
175
|
+
* answer.
|
|
176
|
+
*
|
|
177
|
+
* - `strictEgress`: the Mac must report `egress-policy-v1`, because an agent or
|
|
178
|
+
* helper that predates the policy ignores the `egress` block and would run
|
|
179
|
+
* the build with no allow-list. Only an exact `0`/`false` means "not strict":
|
|
180
|
+
* a missing, null or unrecognised value requires it (fail closed).
|
|
181
|
+
* - `complianceMode` other than exactly `standard` (regulated, or a value this
|
|
182
|
+
* code does not know): strict egress as above, and at least `sandbox`
|
|
183
|
+
* enforcement. No Mac reports a sandbox yet, so a regulated tunnel's builds
|
|
184
|
+
* wait for one rather than run unconfined.
|
|
185
|
+
*
|
|
186
|
+
* A standard-mode tunnel with strict egress off requires nothing, exactly as
|
|
187
|
+
* before, so the server can ship ahead of any agent.
|
|
188
|
+
*/
|
|
189
|
+
export function vpnTunnelRequirements(tunnel) {
|
|
190
|
+
const regulated = tunnel.complianceMode !== "standard";
|
|
191
|
+
const strict = regulated || (tunnel.strictEgress !== 0 && tunnel.strictEgress !== false);
|
|
192
|
+
if (!strict)
|
|
193
|
+
return NO_VPN_REQUIREMENTS;
|
|
194
|
+
return { features: [EGRESS_POLICY_FEATURE], minEnforcementLevel: regulated ? "sandbox" : "none" };
|
|
195
|
+
}
|
|
196
|
+
/** The wire form of requirements, carried in the lease response as
|
|
197
|
+
* `job.requires`: feature ids plus `enforcement:<level>` for a level above
|
|
198
|
+
* `none`. An agent aborts a job with any token it cannot satisfy. */
|
|
199
|
+
export function vpnRequirementTokens(req) {
|
|
200
|
+
// Verbatim, deliberately NOT `canonicalVpnFeatures`: that drops malformed ids
|
|
201
|
+
// and truncates at MAX_VPN_FEATURES, which is right for untrusted agent-reported
|
|
202
|
+
// input but would silently remove a REQUIRED feature here (fail open). A
|
|
203
|
+
// malformed or surplus required id must survive so `unmetRequirements` flags it
|
|
204
|
+
// as unmet — matching the guarded SQL, which binds the raw list.
|
|
205
|
+
const tokens = [...new Set(req.features)].sort();
|
|
206
|
+
if (enforcementLevelRank(req.minEnforcementLevel) > 0)
|
|
207
|
+
tokens.push(`${ENFORCEMENT_REQUIREMENT_PREFIX}${req.minEnforcementLevel}`);
|
|
208
|
+
return tokens;
|
|
209
|
+
}
|
|
210
|
+
/** Printable, bounded rendering of an untrusted token for logs and errors. */
|
|
211
|
+
function describeToken(token) {
|
|
212
|
+
if (typeof token !== "string")
|
|
213
|
+
return "<non-string>";
|
|
214
|
+
return token.replace(/[^\x21-\x7e]/g, "?").slice(0, 80);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Which of `tokens` the capabilities do NOT satisfy. A token is satisfied only
|
|
218
|
+
* if it is a feature id the capabilities list, or `enforcement:<level>` with a
|
|
219
|
+
* KNOWN level the capabilities reach. An unknown token, an unknown level, or a
|
|
220
|
+
* non-string entry is unmet: this is what makes the check fail closed against a
|
|
221
|
+
* requirement introduced by a newer control plane than the agent. A non-array
|
|
222
|
+
* `tokens` is itself unmet (reported as one entry).
|
|
223
|
+
*
|
|
224
|
+
* Used by both the scheduler (`agentSatisfiesVpnRequirements`) and the agent
|
|
225
|
+
* (before it starts a leased VPN job), so they cannot disagree.
|
|
226
|
+
*/
|
|
227
|
+
export function unmetRequirements(tokens, capabilities) {
|
|
228
|
+
if (!Array.isArray(tokens))
|
|
229
|
+
return ["<malformed requires>"];
|
|
230
|
+
const have = new Set(capabilities.features);
|
|
231
|
+
const haveRank = enforcementLevelRank(capabilities.enforcementLevel);
|
|
232
|
+
const unmet = [];
|
|
233
|
+
for (const token of tokens) {
|
|
234
|
+
if (typeof token === "string" && token.startsWith(ENFORCEMENT_REQUIREMENT_PREFIX)) {
|
|
235
|
+
const level = token.slice(ENFORCEMENT_REQUIREMENT_PREFIX.length);
|
|
236
|
+
const known = ENFORCEMENT_LEVELS.includes(level);
|
|
237
|
+
if (!known || haveRank < enforcementLevelRank(level))
|
|
238
|
+
unmet.push(describeToken(token));
|
|
239
|
+
}
|
|
240
|
+
else if (typeof token !== "string" || !VPN_FEATURE_ID_PATTERN.test(token) || !have.has(token)) {
|
|
241
|
+
unmet.push(describeToken(token));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return unmet;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Operator taint (`Agent.dedicatedOrgId`): a Mac dedicated to an org takes only
|
|
248
|
+
* that org's builds, VPN or not. `null` = shared. An empty string is NOT shared:
|
|
249
|
+
* only an explicit null is (a mangled value must not widen who may build here).
|
|
250
|
+
*/
|
|
251
|
+
export function agentAllowsOrg(dedicatedOrgId, buildOrgId) {
|
|
252
|
+
return dedicatedOrgId === null || dedicatedOrgId === buildOrgId;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Where a tunnel's builds may run (`VpnTunnel.hostPolicy`, migrations/0047).
|
|
256
|
+
* - `shared` any approved, VPN-capable Mac, under the exclusive-host lock
|
|
257
|
+
* (today's behaviour, and the default).
|
|
258
|
+
* - `pinned` only Macs the operator pinned to the tunnel (node affinity).
|
|
259
|
+
* - `dedicated` pinned, AND the Mac is dedicated to this org
|
|
260
|
+
* (`Agent.dedicatedOrgId`, so no other org's work is leased to
|
|
261
|
+
* it), AND it reports no co-hosted GitHub Actions runner.
|
|
262
|
+
* Set only by a platform admin; org admins can read it, never write it.
|
|
263
|
+
*/
|
|
264
|
+
export const VPN_HOST_POLICIES = ["shared", "pinned", "dedicated"];
|
|
265
|
+
/**
|
|
266
|
+
* What a customer sees for their tunnel's host policy (read-only; the operator
|
|
267
|
+
* sets it). Deliberately plain about the limits: "dedicated" is a scheduling
|
|
268
|
+
* guarantee about which machines AppForge runs this org's VPN builds on, not a
|
|
269
|
+
* claim of hardware isolation or of any compliance certification.
|
|
270
|
+
*/
|
|
271
|
+
export function describeVpnHostPolicy(policy, pinnedHostCount = 0) {
|
|
272
|
+
const n = `${pinnedHostCount} build machine${pinnedHostCount === 1 ? "" : "s"}`;
|
|
273
|
+
switch (parseVpnHostPolicy(policy)) {
|
|
274
|
+
case "shared":
|
|
275
|
+
return "shared: builds may run on any approved AppForge build machine, one organization's VPN tunnel at a time";
|
|
276
|
+
case "pinned":
|
|
277
|
+
return `pinned: VPN builds run only on the ${n} AppForge assigned to your tunnel`;
|
|
278
|
+
case "dedicated":
|
|
279
|
+
return `dedicated: VPN builds run only on the ${n} AppForge assigned to your tunnel, which take no other organization's builds. This is a scheduling arrangement, not a compliance certification`;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/** How long a queued VPN build of a `pinned`/`dedicated` tunnel waits for a ready
|
|
283
|
+
* pinned Mac before the control plane's sweep fails it as an infra failure with a
|
|
284
|
+
* refund. The wait is measured as CONTINUOUS host unavailability, not build age:
|
|
285
|
+
* the sweep records when the tunnel's pinned Macs stopped being ready
|
|
286
|
+
* (`VpnTunnelHostWait`) and fails a build only once they have all been unready
|
|
287
|
+
* for this long AND the build itself has been queued this long. Any tick that
|
|
288
|
+
* finds a ready Mac (busy counts as ready) restarts the clock, so a Mac that is
|
|
289
|
+
* briefly away (a reboot or self-update cycle, a short drain) does not fail a
|
|
290
|
+
* build that has merely been waiting in line. Longer than the 120 s
|
|
291
|
+
* lock-reservation window. The sweep runs every 5 minutes, so the effective
|
|
292
|
+
* wait is this plus up to one cron interval. */
|
|
293
|
+
export const VPN_HOST_WAIT_SECONDS = 600;
|
|
294
|
+
/** If the sweep's last observation of a tunnel's pinned hosts is older than this
|
|
295
|
+
* (three cron ticks), it knows nothing about the gap, so the next "unready"
|
|
296
|
+
* observation starts a fresh unavailability stretch instead of extending an old one. */
|
|
297
|
+
export const VPN_HOST_OBSERVATION_GAP_SECONDS = 900;
|
|
298
|
+
/**
|
|
299
|
+
* Parses a stored policy. An UNKNOWN value is `dedicated`, the strictest, not
|
|
300
|
+
* `shared`: a mangled policy must tighten placement, never loosen it (the
|
|
301
|
+
* column's CHECK makes this unreachable today; it is here so a future value
|
|
302
|
+
* read by an older Worker fails closed).
|
|
303
|
+
*/
|
|
304
|
+
export function parseVpnHostPolicy(raw) {
|
|
305
|
+
return typeof raw === "string" && VPN_HOST_POLICIES.includes(raw) ? raw : "dedicated";
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Whether an agent may serve a VPN build under the tunnel's host policy.
|
|
309
|
+
* - `hostPinned` is "this Mac is in the tunnel's pin set".
|
|
310
|
+
* - `dedicatedOrgId` is the Mac's operator taint.
|
|
311
|
+
* - `coHostedRunner` is what its last (fresh) heartbeat reported: only an
|
|
312
|
+
* exact 0 means "no GitHub Actions runner on the machine". 1, null
|
|
313
|
+
* (unreported) and anything else all refuse for `dedicated`.
|
|
314
|
+
* `shared` always passes (and is the only case that needs no pin).
|
|
315
|
+
*/
|
|
316
|
+
export function agentSatisfiesHostPolicy(policy, orgId, agent) {
|
|
317
|
+
const p = parseVpnHostPolicy(policy);
|
|
318
|
+
if (p === "shared")
|
|
319
|
+
return true;
|
|
320
|
+
if (!agent.hostPinned)
|
|
321
|
+
return false;
|
|
322
|
+
if (p === "pinned")
|
|
323
|
+
return true;
|
|
324
|
+
return agent.dedicatedOrgId === orgId && agent.coHostedRunner === 0;
|
|
325
|
+
}
|
|
103
326
|
/**
|
|
104
327
|
* Scheduler predicate for the site-to-site VPN's exclusive-host lock —
|
|
105
328
|
* the "taint/toleration" check, ANDed alongside `agentCanBuildPlatform` in
|
|
106
329
|
* `/agents/lease-job` before any `claimBuildById` attempt:
|
|
107
330
|
*
|
|
331
|
+
* - A Mac dedicated to an org (`dedicatedOrgId`) refuses every other org's
|
|
332
|
+
* builds, VPN or not.
|
|
108
333
|
* - A build that doesn't use the VPN is never offered to a Mac that is
|
|
109
334
|
* locked to an org (the lock means "only that org's VPN builds here").
|
|
110
335
|
* - A VPN build needs a VPN-capable AND admin-approved Mac (issue #643), a tunnel that exists and isn't
|
|
111
336
|
* `disabled`, and a Mac that is either unlocked or already locked to
|
|
112
337
|
* this same org (same-org VPN builds share the lock).
|
|
338
|
+
* - A VPN build also needs a Mac that satisfies the tunnel's requirements:
|
|
339
|
+
* it reports every required feature and a high enough enforcement level
|
|
340
|
+
* (capability negotiation, migrations/0043). Nothing is required today.
|
|
341
|
+
* - A VPN build whose tunnel is `pinned` or `dedicated` (migrations/0047) only
|
|
342
|
+
* goes to a Mac pinned to that tunnel, and `dedicated` additionally needs the
|
|
343
|
+
* Mac dedicated to the org with no co-hosted GitHub Actions runner.
|
|
113
344
|
*
|
|
114
345
|
* This is an optimization, not the safety net: it filters on the agent
|
|
115
346
|
* row loaded at the start of the request, which can be stale by the time
|
|
@@ -120,14 +351,76 @@ export function agentCanBuildPlatform(agent, platform, now = new Date()) {
|
|
|
120
351
|
* proves it works.
|
|
121
352
|
*/
|
|
122
353
|
export function agentSatisfiesVpnRequirements(agent, build) {
|
|
354
|
+
if (!agentAllowsOrg(agent.dedicatedOrgId, build.orgId))
|
|
355
|
+
return false;
|
|
123
356
|
if (!build.useVpn)
|
|
124
357
|
return agent.vpnLockOrgId === null;
|
|
125
358
|
if (agent.vpnCapable !== 1 || agent.vpnApproved !== 1)
|
|
126
359
|
return false;
|
|
127
360
|
if (build.vpnTunnelStatus === null || build.vpnTunnelStatus === "disabled")
|
|
128
361
|
return false;
|
|
362
|
+
if (unmetRequirements(vpnRequirementTokens(build.requirements), { features: agent.vpnFeatures, enforcementLevel: agent.enforcementLevel }).length > 0)
|
|
363
|
+
return false;
|
|
364
|
+
if (!agentSatisfiesHostPolicy(build.hostPolicy, build.orgId, {
|
|
365
|
+
hostPinned: build.agentPinned,
|
|
366
|
+
dedicatedOrgId: agent.dedicatedOrgId,
|
|
367
|
+
coHostedRunner: agent.coHostedRunner,
|
|
368
|
+
})) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
129
371
|
return agent.vpnLockOrgId === null || agent.vpnLockOrgId === build.orgId;
|
|
130
372
|
}
|
|
373
|
+
/** The first agent release that can enforce a VPN job (it ships the signed
|
|
374
|
+
* WireGuard helper and honours `job.vpn`). Anything older IGNORES `job.vpn`
|
|
375
|
+
* and would run the build with no tunnel, so it must never be leased one,
|
|
376
|
+
* whatever its (possibly stale) `Agent.vpnCapable` row says. */
|
|
377
|
+
export const MIN_VPN_AGENT_VERSION = "0.2.0";
|
|
378
|
+
/** How recent `Agent.lastSeenAt` must be for `vpnCapable` to count: the flag
|
|
379
|
+
* is only as good as the heartbeat that last set it. Three missed 15s beats
|
|
380
|
+
* plus slack — and well under the 60s-ish scheduler cadence's tolerance. */
|
|
381
|
+
export const VPN_CAPABILITY_MAX_AGE_SECONDS = 120;
|
|
382
|
+
/**
|
|
383
|
+
* Numeric `major.minor.patch` comparison (a `-prerelease`/`+build` suffix is
|
|
384
|
+
* ignored). Returns <0, 0, >0, or `null` when either side isn't a plain
|
|
385
|
+
* semver — callers treat `null` as "unknown", never as "new enough".
|
|
386
|
+
*/
|
|
387
|
+
export function compareSemver(a, b) {
|
|
388
|
+
const parse = (v) => {
|
|
389
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(v.trim());
|
|
390
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
391
|
+
};
|
|
392
|
+
const pa = parse(a);
|
|
393
|
+
const pb = parse(b);
|
|
394
|
+
if (!pa || !pb)
|
|
395
|
+
return null;
|
|
396
|
+
for (let i = 0; i < 3; i++) {
|
|
397
|
+
if (pa[i] !== pb[i])
|
|
398
|
+
return pa[i] - pb[i];
|
|
399
|
+
}
|
|
400
|
+
return 0;
|
|
401
|
+
}
|
|
402
|
+
/** True only for a known version >= `MIN_VPN_AGENT_VERSION`. A missing or
|
|
403
|
+
* unparseable version is NOT capable (fail closed). */
|
|
404
|
+
export function agentVersionSupportsVpn(agentVersion) {
|
|
405
|
+
if (!agentVersion)
|
|
406
|
+
return false;
|
|
407
|
+
const cmp = compareSemver(agentVersion, MIN_VPN_AGENT_VERSION);
|
|
408
|
+
return cmp !== null && cmp >= 0;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Server-side proof that the leasing agent can actually enforce a VPN job,
|
|
412
|
+
* independent of the sticky-ish `vpnCapable` bit: the agent's reported
|
|
413
|
+
* version must be >= `MIN_VPN_AGENT_VERSION` and its last heartbeat (which is
|
|
414
|
+
* what (re-)asserts `vpnCapable`) must be recent. ANDed with
|
|
415
|
+
* `agentSatisfiesVpnRequirements` in `/agents/lease-job`; the guarded SQL in
|
|
416
|
+
* `claimBuildById`/`acquireVpnLock` re-checks freshness and pins the version.
|
|
417
|
+
*/
|
|
418
|
+
export function agentProvesVpnEnforcement(agent, now = new Date()) {
|
|
419
|
+
if (!agentVersionSupportsVpn(agent.agentVersion))
|
|
420
|
+
return false;
|
|
421
|
+
const ageMs = now.getTime() - parseUtc(agent.lastSeenAt).getTime();
|
|
422
|
+
return Number.isFinite(ageMs) && ageMs <= VPN_CAPABILITY_MAX_AGE_SECONDS * 1000;
|
|
423
|
+
}
|
|
131
424
|
/**
|
|
132
425
|
* The currently published packages/agent build — bump this in lockstep
|
|
133
426
|
* with packages/agent/package.json's own "version" field (and
|
|
@@ -141,7 +434,7 @@ export function agentSatisfiesVpnRequirements(agent, build) {
|
|
|
141
434
|
* version against this same constant to decide whether to auto-issue an
|
|
142
435
|
* `UPDATE_AGENT` command (Phase 2's remote command, reused unchanged).
|
|
143
436
|
*/
|
|
144
|
-
export const CURRENT_AGENT_VERSION = "0.
|
|
437
|
+
export const CURRENT_AGENT_VERSION = "0.3.0";
|
|
145
438
|
/**
|
|
146
439
|
* Whether a fleet member is running the latest known agent build.
|
|
147
440
|
* `null` — deliberately not "outdated" — for a row with no reported
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-readiness.js","sourceRoot":"","sources":["../src/agent-readiness.ts"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,QAAQ,CAAC,SAAiB;IACxC,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;AAC3F,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,UAAkB,EAAE,MAAY,IAAI,IAAI,EAAE;IACrE,OAAO,GAAG,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,GAAG,oBAAoB,CAAC;AAC/E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAA+C,EAC/C,MAAY,IAAI,IAAI,EAAE;IAEtB,OAAO,YAAY,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,KAGC,EACD,MAAY,IAAI,IAAI,EAAE;IAEtB,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,KAAK,IAAI,IAAI,KAAK,CAAC,iBAAiB,KAAK,IAAI,CAAC;IACpF,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,KAAK,IAAI,IAAI,KAAK,CAAC,gBAAgB,KAAK,IAAI,CAAC;IAChF,MAAM,YAAY,GAAG,KAAK,CAAC,cAAc,KAAK,IAAI,IAAI,CAAC,YAAY,IAAI,QAAQ,CAAC,CAAC;IACjF,uEAAuE;IACvE,wEAAwE;IACxE,qDAAqD;IACrD,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,UAAU,CAAC;IAC7D,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,IAAI,CAAC,YAAY,IAAI,QAAQ,CAAC,EAAE,CAAC;AACzG,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAyB,EAAE,QAAkB;IAC7E,IAAI,CAAC,SAAS,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IACrC,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC,YAAY,CAAC;QAChC,KAAK,KAAK;YACR,OAAO,SAAS,CAAC,QAAQ,CAAC;QAC5B,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC,YAAY,CAAC;QAChC,KAAK,cAAc;YACjB,OAAO,SAAS,CAAC,YAAY,IAAI,SAAS,CAAC,QAAQ,CAAC;IACxD,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAkD,EAClD,QAAkB,EAClB,MAAY,IAAI,IAAI,EAAE;IAEtB,OAAO,iBAAiB,CAAC,qBAAqB,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;AACxE,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-readiness.js","sourceRoot":"","sources":["../src/agent-readiness.ts"],"names":[],"mappings":"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"}
|