@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.
Files changed (60) hide show
  1. package/dist/agent-readiness.d.ts +232 -1
  2. package/dist/agent-readiness.d.ts.map +1 -1
  3. package/dist/agent-readiness.js +294 -1
  4. package/dist/agent-readiness.js.map +1 -1
  5. package/dist/agent-readiness.test.js +253 -19
  6. package/dist/agent-readiness.test.js.map +1 -1
  7. package/dist/api-client.d.ts +42 -3
  8. package/dist/api-client.d.ts.map +1 -1
  9. package/dist/api-client.js +27 -0
  10. package/dist/api-client.js.map +1 -1
  11. package/dist/api-client.test.js +22 -0
  12. package/dist/api-client.test.js.map +1 -1
  13. package/dist/attestation.d.ts +265 -0
  14. package/dist/attestation.d.ts.map +1 -0
  15. package/dist/attestation.js +327 -0
  16. package/dist/attestation.js.map +1 -0
  17. package/dist/attestation.test.d.ts +2 -0
  18. package/dist/attestation.test.d.ts.map +1 -0
  19. package/dist/attestation.test.js +218 -0
  20. package/dist/attestation.test.js.map +1 -0
  21. package/dist/build-vpn.d.ts +10 -0
  22. package/dist/build-vpn.d.ts.map +1 -0
  23. package/dist/build-vpn.js +23 -0
  24. package/dist/build-vpn.js.map +1 -0
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +5 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/schemas.d.ts +342 -7
  30. package/dist/schemas.d.ts.map +1 -1
  31. package/dist/schemas.js +92 -2
  32. package/dist/schemas.js.map +1 -1
  33. package/dist/types.d.ts +149 -0
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/types.js.map +1 -1
  36. package/dist/vpn-egress-presets.d.ts +142 -0
  37. package/dist/vpn-egress-presets.d.ts.map +1 -0
  38. package/dist/vpn-egress-presets.js +318 -0
  39. package/dist/vpn-egress-presets.js.map +1 -0
  40. package/dist/vpn-egress-presets.test.d.ts +2 -0
  41. package/dist/vpn-egress-presets.test.d.ts.map +1 -0
  42. package/dist/vpn-egress-presets.test.js +210 -0
  43. package/dist/vpn-egress-presets.test.js.map +1 -0
  44. package/dist/vpn-egress.d.ts +57 -0
  45. package/dist/vpn-egress.d.ts.map +1 -0
  46. package/dist/vpn-egress.js +126 -0
  47. package/dist/vpn-egress.js.map +1 -0
  48. package/dist/vpn-egress.test.d.ts +2 -0
  49. package/dist/vpn-egress.test.d.ts.map +1 -0
  50. package/dist/vpn-egress.test.js +133 -0
  51. package/dist/vpn-egress.test.js.map +1 -0
  52. package/dist/vpn-ssh.d.ts +45 -0
  53. package/dist/vpn-ssh.d.ts.map +1 -0
  54. package/dist/vpn-ssh.js +108 -0
  55. package/dist/vpn-ssh.js.map +1 -0
  56. package/dist/vpn-ssh.test.d.ts +2 -0
  57. package/dist/vpn-ssh.test.d.ts.map +1 -0
  58. package/dist/vpn-ssh.test.js +75 -0
  59. package/dist/vpn-ssh.test.js.map +1 -0
  60. 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.2.3";
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;CAC7B;AAED;uDACuD;AACvD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,sEAAsE;IACtE,eAAe,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,IAAI,CAAC;CACvE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAK3G;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,UAAU,CAAC;AAE7C;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,CAGjF;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CAAC,qBAAqB,EAAE,MAAM,GAAG,IAAI,EAAE,sBAAsB,EAAE,OAAO,GAAG,OAAO,CAEpH;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAOzD"}
1
+ {"version":3,"file":"agent-readiness.d.ts","sourceRoot":"","sources":["../src/agent-readiness.ts"],"names":[],"mappings":"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"}
@@ -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.2.3";
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":"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"}
1
+ {"version":3,"file":"agent-readiness.js","sourceRoot":"","sources":["../src/agent-readiness.ts"],"names":[],"mappings":"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"}