@appforge-ci/core 0.1.1

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent-readiness.d.ts +163 -0
  3. package/dist/agent-readiness.d.ts.map +1 -0
  4. package/dist/agent-readiness.js +179 -0
  5. package/dist/agent-readiness.js.map +1 -0
  6. package/dist/agent-readiness.test.d.ts +2 -0
  7. package/dist/agent-readiness.test.d.ts.map +1 -0
  8. package/dist/agent-readiness.test.js +177 -0
  9. package/dist/agent-readiness.test.js.map +1 -0
  10. package/dist/api-client.d.ts +529 -0
  11. package/dist/api-client.d.ts.map +1 -0
  12. package/dist/api-client.js +551 -0
  13. package/dist/api-client.js.map +1 -0
  14. package/dist/api-client.test.d.ts +2 -0
  15. package/dist/api-client.test.d.ts.map +1 -0
  16. package/dist/api-client.test.js +305 -0
  17. package/dist/api-client.test.js.map +1 -0
  18. package/dist/build-cost.test.d.ts +2 -0
  19. package/dist/build-cost.test.d.ts.map +1 -0
  20. package/dist/build-cost.test.js +21 -0
  21. package/dist/build-cost.test.js.map +1 -0
  22. package/dist/index.d.ts +8 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +8 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/ota-artifact.d.ts +41 -0
  27. package/dist/ota-artifact.d.ts.map +1 -0
  28. package/dist/ota-artifact.js +43 -0
  29. package/dist/ota-artifact.js.map +1 -0
  30. package/dist/ota-artifact.test.d.ts +2 -0
  31. package/dist/ota-artifact.test.d.ts.map +1 -0
  32. package/dist/ota-artifact.test.js +44 -0
  33. package/dist/ota-artifact.test.js.map +1 -0
  34. package/dist/ota-bundle-hash.d.ts +38 -0
  35. package/dist/ota-bundle-hash.d.ts.map +1 -0
  36. package/dist/ota-bundle-hash.js +40 -0
  37. package/dist/ota-bundle-hash.js.map +1 -0
  38. package/dist/ota-bundle-hash.test.d.ts +2 -0
  39. package/dist/ota-bundle-hash.test.d.ts.map +1 -0
  40. package/dist/ota-bundle-hash.test.js +28 -0
  41. package/dist/ota-bundle-hash.test.js.map +1 -0
  42. package/dist/ota-rollout.d.ts +38 -0
  43. package/dist/ota-rollout.d.ts.map +1 -0
  44. package/dist/ota-rollout.js +53 -0
  45. package/dist/ota-rollout.js.map +1 -0
  46. package/dist/ota-rollout.test.d.ts +2 -0
  47. package/dist/ota-rollout.test.d.ts.map +1 -0
  48. package/dist/ota-rollout.test.js +48 -0
  49. package/dist/ota-rollout.test.js.map +1 -0
  50. package/dist/schemas.d.ts +776 -0
  51. package/dist/schemas.d.ts.map +1 -0
  52. package/dist/schemas.js +538 -0
  53. package/dist/schemas.js.map +1 -0
  54. package/dist/token-packs.test.d.ts +2 -0
  55. package/dist/token-packs.test.d.ts.map +1 -0
  56. package/dist/token-packs.test.js +34 -0
  57. package/dist/token-packs.test.js.map +1 -0
  58. package/dist/types.d.ts +445 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +80 -0
  61. package/dist/types.js.map +1 -0
  62. package/package.json +35 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,538 @@
1
+ import { z } from "zod";
2
+ export const platformSchema = z.enum(["ios", "android", "flutter", "react-native"]);
3
+ export const appforgeWorkflowSchema = z.object({
4
+ name: z.string().optional(),
5
+ environment: z
6
+ .object({
7
+ node: z.string().optional(),
8
+ flutter: z.string().optional(),
9
+ xcode: z.string().optional(),
10
+ vars: z.record(z.string()).optional(),
11
+ })
12
+ .optional(),
13
+ triggers: z
14
+ .object({
15
+ push: z.object({ branch: z.array(z.string()) }).optional(),
16
+ })
17
+ .optional(),
18
+ scripts: z
19
+ .array(z.object({
20
+ name: z.string().optional(),
21
+ script: z.string().min(1),
22
+ }))
23
+ .min(1),
24
+ artifacts: z.array(z.string()).optional(),
25
+ /** Opt-in post-build smoke test (#9 v1 — installs the built .apk or
26
+ * zipped Simulator .app, launches it, screenshots it) — see
27
+ * packages/agent/src/lib/android-smoke-test.ts and ios-smoke-test.ts.
28
+ * Off by default; never changes existing workflows' behavior. */
29
+ smokeTest: z.boolean().optional(),
30
+ /** Opt-in OTA (over-the-air) code-push update publishing. Off by default. */
31
+ ota: z
32
+ .object({
33
+ channel: z.string().default("production"),
34
+ mandatory: z.boolean().default(false),
35
+ })
36
+ .optional(),
37
+ });
38
+ /** Validates a parsed appforge.yaml document. */
39
+ export const appforgeConfigSchema = z.object({
40
+ platform: platformSchema,
41
+ workflows: z.record(appforgeWorkflowSchema).refine((w) => Object.keys(w).length > 0, {
42
+ message: "at least one workflow is required",
43
+ }),
44
+ });
45
+ // `.trim().toLowerCase()` before `.email()` — security-relevant, not just
46
+ // tidiness. `apps/api/src/lib/middleware.ts`'s `isPlatformAdminEmail`
47
+ // already lowercases the email it's checking against
48
+ // `PLATFORM_ADMIN_EMAILS` (also stored lowercased), but nothing upstream
49
+ // of it — this schema, `createUser`/`getUserByEmail` (packages/db/src/
50
+ // users.ts), or the `User.email` column itself (`TEXT NOT NULL UNIQUE`,
51
+ // SQLite's default case-sensitive BINARY collation) — ever normalized
52
+ // case before this fix. That let a case-variant of a real platform
53
+ // admin's email (`Admin@Company.com` vs. the allowlisted
54
+ // `admin@company.com`) register as a *genuinely distinct* `User` row
55
+ // (the case-sensitive UNIQUE index doesn't dedupe them) that still
56
+ // passed `isPlatformAdminEmail`'s own lowercased check — full
57
+ // `requirePlatformAdmin` access (fleet enroll/drain/reboot/cancel-build/
58
+ // remote-control, every route in `apps/admin-api`) to anyone who knew an
59
+ // admin's email address, which `docs/self-hosting.md` tells operators to
60
+ // configure and which is often no secret at all. Normalizing here closes
61
+ // it for every future signup/login unconditionally; see
62
+ // `migrations/0026_lowercase_user_email.sql` for backfilling any
63
+ // already-mixed-case row.
64
+ const emailSchema = z.string().trim().toLowerCase().email();
65
+ // `name`/`orgName` had no upper bound at all until this fix — signup is
66
+ // unauthenticated and free (no payment method needed, see #19's free
67
+ // signup credit), so any caller could submit an arbitrarily large string
68
+ // here (megabytes+), which then gets stored in D1, echoed back in every
69
+ // `/orgs`/`/me`-shaped API response, and rendered directly in the
70
+ // dashboard. 255 matches the bound this same fix applies to the
71
+ // equivalent `App`/`Org` name fields below (`createAppSchema`/
72
+ // `updateAppSchema`/`updateOrgSchema`) — plenty for a real display name,
73
+ // nowhere near enough to matter as a storage/response-size abuse vector.
74
+ export const signupSchema = z.object({
75
+ email: emailSchema,
76
+ password: z.string().min(10, "password must be at least 10 characters"),
77
+ name: z.string().min(1).max(255, "must be at most 255 characters").optional(),
78
+ orgName: z.string().min(1).max(255, "must be at most 255 characters"),
79
+ });
80
+ export const loginSchema = z.object({
81
+ email: emailSchema,
82
+ password: z.string().min(1),
83
+ /** Only required when the account has TOTP 2FA enabled — see
84
+ * routes/auth.ts's login handler. Mutually exclusive with
85
+ * `recoveryCode` in practice (the handler tries `recoveryCode` first
86
+ * when both are somehow present), never both required at once. */
87
+ totpCode: z.string().optional(),
88
+ /** Alternate to `totpCode` for a lost authenticator — one of
89
+ * `TotpRecoveryCode`'s single-use codes (migrations/
90
+ * 0008_totp_recovery_codes.sql). */
91
+ recoveryCode: z.string().optional(),
92
+ });
93
+ export const verifyEmailSchema = z.object({
94
+ token: z.string().min(1),
95
+ });
96
+ /** `POST /auth/2fa/confirm` requires re-proving the account password —
97
+ * the same "a stolen session token alone isn't enough to mutate this"
98
+ * reasoning as `disableTotpSchema` below, applied to the opposite
99
+ * (enable) side. Without this, a stolen/leaked session token (JWT or
100
+ * API token — either satisfies `requireUser`) alone was enough to
101
+ * enroll and confirm 2FA with an attacker-controlled secret: `confirm`
102
+ * is the call that actually flips `User.totpEnabledAt` and hands back
103
+ * the only copy of the recovery codes, and once it's flipped,
104
+ * `POST /auth/login` refuses the real account owner (who has neither
105
+ * the attacker's authenticator nor their recovery codes) — a
106
+ * self-inflicted, unrecoverable lockout worse than the already-guarded
107
+ * "stolen token disables 2FA" case, since this codebase has no
108
+ * account-recovery flow. See docs/security.md's "2FA (TOTP)" note. */
109
+ export const confirmTotpSchema = z.object({
110
+ code: z.string().min(6).max(6),
111
+ password: z.string().min(1),
112
+ });
113
+ /** `POST /auth/2fa/disable` requires re-proving the account password —
114
+ * without this, a stolen/leaked session token (JWT or API token) alone
115
+ * was enough to turn off 2FA, defeating the point of having it. See
116
+ * docs/security.md's "2FA (TOTP)" note. */
117
+ export const disableTotpSchema = z.object({
118
+ password: z.string().min(1),
119
+ });
120
+ /** `POST /auth/2fa/recovery-codes/regenerate` requires re-proving the
121
+ * account password — the one mutating 2FA route in `two-factor.ts`
122
+ * that didn't get this treatment when `confirmTotpSchema`/
123
+ * `disableTotpSchema` above did. Without it, a stolen/leaked session
124
+ * token alone (JWT or API token — either satisfies `requireUser`, and
125
+ * an API token "never expires on its own") was enough to silently
126
+ * invalidate every one of the real owner's existing recovery codes and
127
+ * have the fresh set handed back to the attacker instead — two
128
+ * independent harms in one call: it silently destroys the owner's own
129
+ * recovery path (this codebase has no account-recovery flow, so a
130
+ * lost authenticator plus a now-invalid saved code is an unrecoverable
131
+ * lockout, with zero signal anywhere that this happened), and it hands
132
+ * the attacker 10 valid recovery codes that let them satisfy the 2FA
133
+ * challenge at `POST /auth/login` later, using nothing but the
134
+ * password itself — defeating the entire point of 2FA. See
135
+ * docs/security.md's "2FA (TOTP)" note. */
136
+ export const regenerateRecoveryCodesSchema = z.object({
137
+ password: z.string().min(1),
138
+ });
139
+ /** `POST /auth/change-password` — same "re-prove the account password
140
+ * before mutating it" reasoning as `disableTotpSchema` above: a stolen
141
+ * session token alone must not be enough to rotate the one credential
142
+ * every other recovery path (2FA disable, this route itself) treats as
143
+ * the account's ultimate fallback. `newPassword` uses the same
144
+ * `min(10)` rule `signupSchema` enforces at signup. */
145
+ export const changePasswordSchema = z.object({
146
+ currentPassword: z.string().min(1),
147
+ newPassword: z.string().min(10, "password must be at least 10 characters"),
148
+ });
149
+ /** `POST /auth/forgot-password` — deliberately just an email, no
150
+ * password: this is the unauthenticated "I forgot it" entry point
151
+ * (issue-and-email-a-token side of the pair below), same shape as
152
+ * `verifyEmailSchema`'s issuing sibling `resend-verification` (which
153
+ * takes no body at all, since it's already `requireUser`-gated; this
154
+ * route isn't, so it needs the email to know who to email). */
155
+ export const forgotPasswordSchema = z.object({
156
+ email: emailSchema,
157
+ });
158
+ /** `POST /auth/reset-password` — redeems the token minted by
159
+ * `forgot-password` above. `newPassword` uses the same `min(10)` rule
160
+ * `changePasswordSchema`/`signupSchema` enforce everywhere else a
161
+ * password is ever set. */
162
+ export const resetPasswordSchema = z.object({
163
+ token: z.string().min(1),
164
+ newPassword: z.string().min(10, "password must be at least 10 characters"),
165
+ });
166
+ // `.url()` alone only checks that the string parses via the WHATWG `URL`
167
+ // constructor — it does NOT restrict the scheme. `file:`, `ext:`,
168
+ // `ssh:`, and `git:` all parse as valid URLs just as readily as
169
+ // `https:` does. `repoUrl` is stored verbatim (`packages/db/src/
170
+ // apps.ts`) and, once a build is leased, handed straight to a real
171
+ // `git clone` on a shared Mac-mini build slot
172
+ // (`packages/agent/src/lib/builder.ts`'s `runScript`/`simpleGit().clone`)
173
+ // — real `git` honors whatever transport the URL names: `file://`
174
+ // clones a local path on that shared host outright, and `git://`/
175
+ // `ssh://` attempt a real network connection to wherever the URL
176
+ // points — an SSRF vector against any host that Mac mini can otherwise
177
+ // reach (verified directly against a real `git` binary: an
178
+ // unrestricted `git://` clone genuinely attempts a DNS lookup/
179
+ // connection before failing). Git's own `ext::<command>` transport is
180
+ // worse still — it runs an arbitrary shell command as the "clone"
181
+ // itself, real RCE — though verified that a current `git` already
182
+ // refuses it by default; not every Mac mini in the fleet is guaranteed
183
+ // to run a git new enough to have that hardening, so this schema still
184
+ // closes it off rather than depending on git's own version-dependent
185
+ // default. Any signed-up org (no payment method needed — see #19's
186
+ // free signup credit) can set an app's `repoUrl` and trigger a build,
187
+ // so this schema is the actual authorization boundary for what
188
+ // transport ever reaches a real `git clone` on shared, multi-tenant
189
+ // infrastructure (`agents.ts`'s own docblock: "the fleet is a shared
190
+ // pool... not one Mac mini per customer"). This product only ever
191
+ // integrates public GitHub repos over HTTPS (no SSH-deploy-key feature
192
+ // exists anywhere in this codebase), so restricting to exactly `https:`
193
+ // isn't just "safe enough," it's the only scheme any legitimate caller
194
+ // ever needs. See `packages/agent/src/lib/builder.ts`'s own `runScript`
195
+ // git-clone call for the matching defense-in-depth restriction applied
196
+ // at the point of the actual clone, in case a repoUrl ever reaches the
197
+ // agent by some path other than this schema.
198
+ const repoUrlSchema = z
199
+ .string()
200
+ .url()
201
+ .refine((v) => new URL(v).protocol === "https:", "must be an https:// URL — no other transport (file:, ext:, ssh:, git:, ...) is supported");
202
+ export const createAppSchema = z.object({
203
+ name: z.string().min(1).max(255, "must be at most 255 characters"),
204
+ platform: platformSchema,
205
+ repoUrl: repoUrlSchema,
206
+ defaultBranch: z.string().min(1).default("main"),
207
+ });
208
+ // `platform` is deliberately excluded — see updateApp's own docblock in
209
+ // packages/db/src/apps.ts. Every field optional; the route only applies
210
+ // whichever ones the caller actually sent.
211
+ export const updateAppSchema = z.object({
212
+ name: z.string().min(1).max(255, "must be at most 255 characters").optional(),
213
+ repoUrl: repoUrlSchema.optional(),
214
+ defaultBranch: z.string().min(1).optional(),
215
+ });
216
+ // Deliberately narrower than `OrgRow` (packages/db) — `updateOrg`'s own
217
+ // signature takes `Partial<OrgRow>` and applies whatever keys it's
218
+ // given, so this schema (not that function) is what actually keeps a
219
+ // `PATCH /orgs/:orgId` request from touching `tokenBalance`,
220
+ // `lhvCardToken`, or any other billing-internal column, the same way
221
+ // `updateAppSchema` deliberately excludes `platform`.
222
+ export const updateOrgSchema = z.object({
223
+ name: z.string().min(1).max(255, "must be at most 255 characters").optional(),
224
+ // Explicit `null` clears it (disables notifications); omitted leaves
225
+ // whatever's already set unchanged — same "undefined vs. null" update
226
+ // convention this schema's own generic `updateOrg` (packages/db)
227
+ // partial-patch mechanism already relies on elsewhere. `.url()`
228
+ // deliberately doesn't restrict to https-only: a self-hosted operator
229
+ // testing against a local catcher (http://localhost:...) is a
230
+ // legitimate use before pointing this at a real endpoint.
231
+ buildWebhookUrl: z.string().url().nullable().optional(),
232
+ });
233
+ // Build-secrets v1 (ROADMAP.md, split off from #322) — a secret's `name`
234
+ // is injected verbatim as an env var name into every build for the app
235
+ // it's set on (apps/admin-api's /agents/lease-job route,
236
+ // packages/agent's builder.ts), so it's restricted to the same charset
237
+ // a real shell env var name allows (POSIX: starts with a letter/
238
+ // underscore, then letters/digits/underscores) — anything else could
239
+ // never actually work as `$NAME` in a workflow script, so rejecting it
240
+ // up front is a better failure mode than accepting it and silently
241
+ // never being usable. `value` is otherwise unrestricted content (a
242
+ // keystore password, an API token, anything) except for two things:
243
+ // no embedded newline, and a minimum length of 4.
244
+ //
245
+ // No newline: `packages/agent/src/lib/builder.ts`'s log-redaction fix
246
+ // (the `createLineBuffer` this cycle's own fix added) buffers a build
247
+ // step's raw stdout/stderr to `\n`-terminated lines before ever
248
+ // running `redactSecrets` on them, on the explicit assumption that "a
249
+ // secret's own value doesn't itself contain a newline" — true only if
250
+ // this schema actually enforces it. Without this, a multi-line secret
251
+ // (a PEM signing key, a base64-free P12/keystore export, a raw JSON
252
+ // service-account credential — all realistic values for a mobile-CI
253
+ // signing workflow) that gets echoed by a build step and happens to
254
+ // land split across two stdout chunks at one of its own embedded
255
+ // newlines would leak in full: each half, redacted independently,
256
+ // never matches the whole value, reassembling into the plaintext
257
+ // secret once the log is read back — the exact failure shape this
258
+ // cycle's own log-redaction fixes exist to close, reopened through a
259
+ // different trigger. Base64-encode a multi-line secret before storing
260
+ // it if you need one (decode it back inside the workflow script
261
+ // itself).
262
+ //
263
+ // Minimum 4 characters: `redactSecrets` (`builder.ts`) itself skips
264
+ // any secret value under 4 characters entirely (`if (value.length < 4)
265
+ // continue`) — a deliberate choice there, since redacting a one/two-
266
+ // character "secret" would blot out ordinary log text on pure
267
+ // coincidence far too often to be worth it, and offers essentially no
268
+ // confidentiality anyway. But nothing tied that skip threshold to
269
+ // what's actually *storable* as a secret until now: with only
270
+ // `.min(1)` here, an org could set e.g. `{ name: "PIN", value: "123"
271
+ // }`, have it injected into every build's environment exactly like any
272
+ // other secret, and have it never redacted from the persisted log if a
273
+ // step ever echoed it — a real, silent path from "operator sets a
274
+ // secret" to "cleartext in a log any org member can read," defeating
275
+ // the same "no route anywhere returns a secret's decrypted value"
276
+ // invariant the newline restriction above protects. Matching the
277
+ // redaction function's own skip threshold here closes that at the one
278
+ // place a short value could ever enter the system.
279
+ // Upper bound on `value`, added alongside the min-length/no-newline
280
+ // checks above: this field had no ceiling at all, unlike every other
281
+ // free-text field this codebase's build-input schemas validate (e.g.
282
+ // `appendLogSchema.chunk`'s own `.max()`, same file). `value` is
283
+ // injected verbatim as an env var value into every build for the app
284
+ // it's set on — `apps/admin-api/src/routes/agents.ts`'s `/agents/
285
+ // lease-job` route decrypts and inlines it into the job response,
286
+ // `packages/agent/src/lib/builder.ts`'s `runBuild` spreads it straight
287
+ // into the spawned build process's env (`spawn(..., { env: {
288
+ // ...process.env, ...vars } })`). A sufficiently large single env var
289
+ // can blow past the real OS `execve`/env-size ceiling (macOS's
290
+ // `ARG_MAX`), which doesn't fail cleanly at set-time — it makes
291
+ // `spawn`'s own `'error'` event fire on *every future build* for that
292
+ // app, which builder.ts classifies as `failureKind: "infra"` (the
293
+ // shell itself never launched) and auto-refunds. That's a genuine,
294
+ // silent, self-inflicted denial of service: one oversized secret,
295
+ // accepted without complaint through the dashboard's plain
296
+ // `<input type="password">`, permanently breaks every subsequent build
297
+ // for the app with an opaque native spawn failure instead of the clear
298
+ // 400 this schema should have given at the moment the secret was
299
+ // actually set. 64 KiB is comfortably larger than any realistic
300
+ // keystore password, API token, PEM signing key, or JSON service-
301
+ // account credential (even base64-inflated) while staying far under
302
+ // any OS env-var ceiling — generous, not a tight fit, same "reject the
303
+ // pathological case, not the normal one" reasoning `appendLogSchema.
304
+ // chunk`'s own `.max()` already documents.
305
+ export const setAppSecretSchema = z.object({
306
+ // `.max(255)` is defense in depth against one oversized name — same
307
+ // convention `createAppSchema.name`/`signupSchema.orgName` use — not
308
+ // the fix for unbounded distinct-secret *count* per app (see
309
+ // `MAX_APP_SECRETS_PER_APP`, apps/api/src/routes/orgs.ts, and its
310
+ // route's own count check for that; unlike #574/#577's list routes,
311
+ // `/agents/lease-job` needs every secret for a build, so bounding the
312
+ // read side alone wouldn't protect that hot path).
313
+ name: z
314
+ .string()
315
+ .max(255, "must be at most 255 characters")
316
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "must be a valid env var name (letters, digits, underscore; not starting with a digit)"),
317
+ value: z
318
+ .string()
319
+ .min(4, "must be at least 4 characters — a shorter value can never be redacted from build logs (see redactSecrets in packages/agent/src/lib/builder.ts)")
320
+ .max(64 * 1024, "must be at most 64KB")
321
+ .refine((v) => !v.includes("\n"), "secret values can't contain a newline — base64-encode a multi-line secret (a PEM key, a keystore export, a JSON credential) before storing it"),
322
+ });
323
+ // Fleet-secrets v1 (issue #392). Unlike `AppSecret`'s free-form env var
324
+ // names, a `FleetSecret` name is drawn from a closed allowlist, because
325
+ // the two have very different blast radii: an `AppSecret` reaches one
326
+ // app's own builds, whereas a `FleetSecret` is handed to the agent
327
+ // daemon on *every* Mac mini enrolled in the org and baked into
328
+ // `apps/device-stream`'s launchd plist. Constraining the set of storable
329
+ // names at the one write boundary (apps/api's
330
+ // `POST /orgs/:orgId/fleet-secrets`) keeps that broadcast channel from
331
+ // quietly becoming a general-purpose "push arbitrary values onto every
332
+ // machine in the fleet" mechanism — which is what it would be if any
333
+ // name were storable. These are exactly the three values
334
+ // `apps/device-stream` reads at startup (see its own index.ts) and the
335
+ // three issue #392 names; adding a fourth is a deliberate one-line
336
+ // change here plus a matching consumer, not something an API caller can
337
+ // do on their own.
338
+ export const FLEET_SECRET_NAMES = ["JWT_SECRET", "JWT_SECRET_PREVIOUS", "INTERNAL_SERVICE_TOKEN"];
339
+ // `value` carries the same min-length/no-newline rules
340
+ // `setAppSecretSchema` documents above. The newline rule matters
341
+ // independently here: a fleet secret is written into an XML plist
342
+ // (`renderDeviceStreamPlist`), where an embedded newline inside a
343
+ // `<string>` would silently produce a value launchd hands the service
344
+ // with a stray line break in it.
345
+ export const setFleetSecretSchema = z.object({
346
+ name: z.enum(FLEET_SECRET_NAMES),
347
+ value: z
348
+ .string()
349
+ .min(4, "must be at least 4 characters — a shorter value can never be redacted from build logs (see redactSecrets in packages/agent/src/lib/builder.ts)")
350
+ // Same 64KB ceiling `setAppSecretSchema.value` has (see its own
351
+ // docblock) — this schema was added later (#392) and reintroduced
352
+ // the identical unbounded-value gap the earlier fix closed there.
353
+ // Nothing downstream (encryptSecret, the FleetSecret table's plain
354
+ // TEXT columns) rejects an oversized value either, and this one is
355
+ // broadcast to every agent in the org (GET /agents/fleet-secrets)
356
+ // and baked verbatim into a launchd plist <string> — an oversized
357
+ // value fails silently at rotation time instead of cleanly here.
358
+ .max(64 * 1024, "must be at most 64KB")
359
+ .refine((v) => !v.includes("\n"), "secret values can't contain a newline — they're written into a launchd plist <string> as-is"),
360
+ });
361
+ // No ASCII control characters (0x00-0x1F, 0x7F) — covers both the
362
+ // newline-forges-a-fake-log-line case setFleetSecretSchema/
363
+ // setAppSecretSchema's own `.refine` already guards against for a
364
+ // different downstream (a launchd plist <string>), and ANSI escape
365
+ // codes (\x1b), which would corrupt an operator's terminal instead.
366
+ // See issue #614: an org-member-controlled `workflow`/`branch` value
367
+ // is logged raw (packages/agent/src/index.ts's `Leased build ...` line)
368
+ // to a Mac mini fleet shared across every org, before appforge.yaml's
369
+ // own workflow-name validation ever runs.
370
+ const NO_CONTROL_CHARS_RE = /^[^\x00-\x1f\x7f]*$/;
371
+ const noControlChars = (v) => NO_CONTROL_CHARS_RE.test(v);
372
+ const NO_CONTROL_CHARS_MESSAGE = "can't contain control characters (including newlines)";
373
+ export const triggerBuildSchema = z.object({
374
+ appId: z.string().min(1),
375
+ workflow: z.string().min(1).max(255, "must be at most 255 characters").refine(noControlChars, NO_CONTROL_CHARS_MESSAGE),
376
+ branch: z
377
+ .string()
378
+ .min(1)
379
+ .max(255, "must be at most 255 characters")
380
+ .refine(noControlChars, NO_CONTROL_CHARS_MESSAGE)
381
+ .optional(),
382
+ });
383
+ export const topUpSchema = z.object({
384
+ packId: z.string().min(1),
385
+ });
386
+ export const setBillingModeSchema = z.object({
387
+ mode: z.enum(["prepaid", "postpaid_monthly"]),
388
+ });
389
+ export const agentEnrollSchema = z.object({
390
+ enrollmentToken: z.string().min(1),
391
+ name: z.string().min(1),
392
+ // The Mac's hardware UUID — the stable key the control plane dedupes
393
+ // enrollment on (see @appforge-ci/db's upsertAgent). Optional/nullable:
394
+ // absent from agents built before this field existed, and null on a
395
+ // non-Mac host, where the server falls back to matching on hostname.
396
+ machineId: z.string().min(1).nullable().optional(),
397
+ hostname: z.string().min(1),
398
+ macOsVersion: z.string().min(1),
399
+ xcodeVersion: z.string().nullable().optional(),
400
+ flutterVersion: z.string().nullable().optional(),
401
+ javaVersion: z.string().nullable().optional(),
402
+ androidSdkVersion: z.string().nullable().optional(),
403
+ cocoapodsVersion: z.string().nullable().optional(),
404
+ cpuModel: z.string().min(1),
405
+ // Optional/nullable for the same reason as machineId above: absent from
406
+ // an agent binary built before this field existed. A current binary
407
+ // always sends its own real AGENT_VERSION (packages/agent/src/lib/
408
+ // version.ts) — see @appforge-ci/core's isAgentVersionCurrent.
409
+ agentVersion: z.string().min(1).nullable().optional(),
410
+ // Optional/nullable for the same reason: absent from an agent binary
411
+ // built before this field existed. See packages/agent/src/lib/
412
+ // system-info.ts's screenSharingEnabled().
413
+ screenSharingEnabled: z.boolean().nullable().optional(),
414
+ });
415
+ export const agentHeartbeatSchema = z.object({
416
+ status: z.enum(["online", "draining", "building"]),
417
+ cpuUsagePercent: z.number().min(0).max(100),
418
+ memUsagePercent: z.number().min(0).max(100),
419
+ diskFreeGb: z.number().min(0),
420
+ currentBuildId: z.string().nullable().optional(),
421
+ tailscaleHostname: z.string().nullable().optional(),
422
+ // Always present (the agent caches its last-detected values and sends
423
+ // them every tick — see updateAgentHeartbeat's docblock in
424
+ // @appforge/db for why these are a direct overwrite, not optional).
425
+ xcodeVersion: z.string().nullable(),
426
+ flutterVersion: z.string().nullable(),
427
+ javaVersion: z.string().nullable(),
428
+ androidSdkVersion: z.string().nullable(),
429
+ cocoapodsVersion: z.string().nullable(),
430
+ // Nullable and optional, unlike the toolchain fields just above —
431
+ // omitted entirely by a not-yet-upgraded agent binary (updateAgentHeartbeat
432
+ // then COALESCEs, keeping whatever version this row last reported)
433
+ // rather than always resent, since a real build's own version is a
434
+ // constant, not a live-detected value that can legitimately disappear.
435
+ agentVersion: z.string().min(1).nullable().optional(),
436
+ // Same COALESCE-on-omission reasoning as agentVersion just above.
437
+ screenSharingEnabled: z.boolean().nullable().optional(),
438
+ // Concurrent builds (see @appforge-ci/agent's ConcurrentBuildRunner) —
439
+ // both optional/omittable for the same "not-yet-upgraded agent binary"
440
+ // reason as agentVersion/screenSharingEnabled just above; once an agent
441
+ // does send them, apps/admin-api's heartbeat route overwrites directly,
442
+ // never merges (see migrations/0017_agent_concurrency.sql's docblock).
443
+ /** How many builds this Mac will run at once. */
444
+ capacity: z.number().int().min(1).optional(),
445
+ /** Build ids this agent is running right now — its length is always
446
+ * <= capacity. Empty array means idle, distinct from omitting the
447
+ * field entirely (an old agent binary that's never heard of this). */
448
+ activeBuildIds: z.array(z.string()).optional(),
449
+ });
450
+ // `POST /builds/:buildId/logs` (apps/admin-api/src/routes/agents.ts) —
451
+ // one call per flushed line/burst from `packages/agent/src/lib/
452
+ // builder.ts`'s `createLineBuffer`, which itself caps a single
453
+ // *unterminated* line at 1 MiB before flushing it as its own call, and
454
+ // routinely flushes smaller multi-line bursts well under that. `.max()`
455
+ // here is a generous ceiling above that normal range (rejecting outright
456
+ // a single pathological chunk, 400) rather than a tight fit around it —
457
+ // the real cumulative-growth guard is the per-build byte cap
458
+ // `Build.logBytes` enforces route-side (migrations/
459
+ // 0028_build_log_bytes_cap.sql), not this per-chunk bound; this just
460
+ // closes the "one absurdly large chunk in a single call" gap that cap
461
+ // alone wouldn't catch quickly (it only rejects the *next* call once the
462
+ // cumulative total is already over, not the one call that single-
463
+ // handedly blows past it).
464
+ export const appendLogSchema = z.object({
465
+ chunk: z.string().max(4 * 1024 * 1024, "log chunk too large"),
466
+ seq: z.number().int().min(0),
467
+ });
468
+ export const completeBuildSchema = z.object({
469
+ status: z.enum(["succeeded", "failed"]),
470
+ artifacts: z
471
+ .array(z.object({
472
+ fileName: z.string(),
473
+ sizeBytes: z.number().int().min(0),
474
+ storageKey: z.string(),
475
+ }))
476
+ .default([]),
477
+ // Present only for builds run with an OTA-enabled workflow (Flutter/React
478
+ // Native code-push) — see packages/agent/src/lib/builder.ts's otaInfo and
479
+ // @appforge-ci/agent's ApiClient.completeBuild.
480
+ ota: z
481
+ .object({
482
+ channel: z.string().min(1).optional(),
483
+ mandatory: z.boolean().optional(),
484
+ bundleHash: z.string().min(1).optional(),
485
+ })
486
+ .optional(),
487
+ // Only meaningful when status is "failed" — see BuildResult.failureKind's
488
+ // docblock in packages/agent/src/lib/builder.ts for exactly what "infra"
489
+ // covers (deliberately narrow) and apps/admin-api/src/routes/agents.ts's
490
+ // /builds/:buildId/complete for what it drives (an automatic refund).
491
+ // Absent/undefined on a succeeded build, or on an older agent version
492
+ // that predates this field.
493
+ failureKind: z.enum(["user", "infra"]).optional(),
494
+ });
495
+ // `channel` had no upper bound at all — same `.max(255)` convention as
496
+ // `createAppSchema`'s `name`. Defense in depth alongside the pagination
497
+ // fix for `listOtaChannelsForApp`/`GET /apps/:appId/ota/channels`
498
+ // (packages/db/src/ota.ts): this caps how large a single abusive channel
499
+ // string can be, though the actual unbounded-request-cost fix is
500
+ // pagination — this schema has no way to cap how many *distinct*
501
+ // channels an org member creates, only each one's own length.
502
+ export const promoteOtaReleaseSchema = z.object({
503
+ buildId: z.string().min(1),
504
+ channel: z.string().min(1).max(255, "must be at most 255 characters").default("production"),
505
+ mandatory: z.boolean().optional().default(false),
506
+ rollout: z.number().int().min(0).max(100).optional().default(100),
507
+ });
508
+ export const setActiveOtaReleaseSchema = z.object({
509
+ releaseId: z.string().min(1).nullable(),
510
+ });
511
+ // Same 0-100 bound as `promoteOtaReleaseSchema`'s own `rollout` field —
512
+ // this is the fix that lets an existing release's rollout be progressed
513
+ // in place (10% -> 50% -> 100%) without minting a new release id, which
514
+ // would otherwise reshuffle `isInStagedRollout`'s per-install bucketing
515
+ // (packages/core/src/ota-rollout.ts) since it hashes on `releaseId`.
516
+ export const updateOtaReleaseRolloutSchema = z.object({
517
+ rollout: z.number().int().min(0).max(100),
518
+ });
519
+ /** `PATCH /orgs/:orgId/agents/:agentId/remote-control` (apps/admin-api) —
520
+ * the only way `Agent.remoteControlEnabled` ever changes (see
521
+ * @appforge/db's migrations/0024_agent_remote_control.sql). Deliberately
522
+ * not threaded through agentEnrollSchema/agentHeartbeatSchema above:
523
+ * this is an operator policy decision, not something the agent
524
+ * self-reports. */
525
+ export const setAgentRemoteControlSchema = z.object({
526
+ enabled: z.boolean(),
527
+ });
528
+ /** `POST /orgs/:orgId/agents/:agentId/vnc-session` (apps/admin-api)'s
529
+ * request body — optional and defaults to "view" (via `.optional()`,
530
+ * left for the route to interpret as the no-body case), matching every
531
+ * existing caller that sends no body at all today. `mode: "control"` is
532
+ * a *request*, not a grant: the route decides the actually-issued mode
533
+ * server-side from `Agent.remoteControlEnabled` and echoes it back in
534
+ * the response — see that route's own docblock. */
535
+ export const startVncSessionSchema = z.object({
536
+ mode: z.enum(["view", "control"]).optional(),
537
+ });
538
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=token-packs.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-packs.test.d.ts","sourceRoot":"","sources":["../src/token-packs.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { tokenPackDisplay, findTokenPack, TOKEN_PACKS, BUILD_COST_TOKENS } from "./types.js";
4
+ test("tokenPackDisplay: starter pack is the list-price baseline (0% discount)", () => {
5
+ const starter = TOKEN_PACKS.find((p) => p.id === "starter");
6
+ const display = tokenPackDisplay(starter);
7
+ assert.equal(display.priceEur, 10);
8
+ assert.equal(display.pricePerTokenCents, 1);
9
+ assert.equal(display.buildsIncluded, Math.floor(starter.tokens / BUILD_COST_TOKENS));
10
+ assert.equal(display.discountPercent, 0);
11
+ });
12
+ test("tokenPackDisplay: growth/scale/studio discounts match TOKEN_PACKS' own documented rates", () => {
13
+ // Matches the "// 10% off" / "20% off" / "30% off" comments next to
14
+ // TOKEN_PACKS' own definition — this confirms existing intent, not new behavior.
15
+ const expected = { growth: 10, scale: 20, studio: 30 };
16
+ for (const [id, discountPercent] of Object.entries(expected)) {
17
+ const pack = TOKEN_PACKS.find((p) => p.id === id);
18
+ assert.equal(tokenPackDisplay(pack).discountPercent, discountPercent, `${id} pack`);
19
+ }
20
+ });
21
+ test("tokenPackDisplay: buildsIncluded is tokens / BUILD_COST_TOKENS, floored", () => {
22
+ for (const pack of TOKEN_PACKS) {
23
+ assert.equal(tokenPackDisplay(pack).buildsIncluded, Math.floor(pack.tokens / BUILD_COST_TOKENS));
24
+ }
25
+ });
26
+ test("findTokenPack returns the matching pack for a known id", () => {
27
+ const pack = findTokenPack("scale");
28
+ assert.equal(pack?.id, "scale");
29
+ assert.equal(pack?.tokens, 30_000);
30
+ });
31
+ test("findTokenPack returns undefined for an unknown id", () => {
32
+ assert.equal(findTokenPack("does-not-exist"), undefined);
33
+ });
34
+ //# sourceMappingURL=token-packs.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-packs.test.js","sourceRoot":"","sources":["../src/token-packs.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE7F,IAAI,CAAC,yEAAyE,EAAE,GAAG,EAAE;IACnF,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAE,CAAC;IAC7D,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACnC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC;IACrF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yFAAyF,EAAE,GAAG,EAAE;IACnG,oEAAoE;IACpE,iFAAiF;IACjF,MAAM,QAAQ,GAA2B,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC/E,KAAK,MAAM,CAAC,EAAE,EAAE,eAAe,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAE,CAAC;QACnD,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,eAAe,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IACtF,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yEAAyE,EAAE,GAAG,EAAE;IACnF,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC;IACnG,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,wDAAwD,EAAE,GAAG,EAAE;IAClE,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC7D,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC"}