unfazed 0.6.0 → 0.8.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 (54) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +299 -78
  3. package/REFERENCE.md +284 -0
  4. package/dist/cli/login.d.ts +129 -0
  5. package/dist/cli/login.js +596 -0
  6. package/dist/cli/login.js.map +1 -0
  7. package/dist/cli/workflow.d.ts +31 -0
  8. package/dist/cli/workflow.js +157 -0
  9. package/dist/cli/workflow.js.map +1 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +173 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/client.d.ts +26 -5
  14. package/dist/client.js +93 -48
  15. package/dist/client.js.map +1 -1
  16. package/dist/credentials.d.ts +123 -0
  17. package/dist/credentials.js +317 -0
  18. package/dist/credentials.js.map +1 -0
  19. package/dist/errors.d.ts +64 -0
  20. package/dist/errors.js +95 -0
  21. package/dist/errors.js.map +1 -1
  22. package/dist/generated/gql.d.ts +78 -2
  23. package/dist/generated/gql.js +21 -2
  24. package/dist/generated/gql.js.map +1 -1
  25. package/dist/generated/graphql.d.ts +505 -6
  26. package/dist/generated/graphql.js +439 -2
  27. package/dist/generated/graphql.js.map +1 -1
  28. package/dist/index.d.ts +25 -6
  29. package/dist/index.js +23 -5
  30. package/dist/index.js.map +1 -1
  31. package/dist/operations.d.ts +116 -5
  32. package/dist/operations.js +379 -7
  33. package/dist/operations.js.map +1 -1
  34. package/dist/reference.d.ts +18 -0
  35. package/dist/reference.generated.d.ts +2 -0
  36. package/dist/reference.generated.js +5 -0
  37. package/dist/reference.generated.js.map +1 -0
  38. package/dist/reference.js +38 -0
  39. package/dist/reference.js.map +1 -0
  40. package/dist/remote/app.js +2 -1
  41. package/dist/remote/app.js.map +1 -1
  42. package/dist/remote/world.d.ts +2 -2
  43. package/dist/remote/world.js +32 -16
  44. package/dist/remote/world.js.map +1 -1
  45. package/dist/unfazed.d.ts +545 -68
  46. package/dist/unfazed.js +688 -64
  47. package/dist/unfazed.js.map +1 -1
  48. package/dist/wait.d.ts +7 -0
  49. package/dist/wait.js.map +1 -1
  50. package/dist/workflow/nitro.js +5 -3
  51. package/dist/workflow/nitro.js.map +1 -1
  52. package/package.json +22 -6
  53. package/schema/SOURCE.json +4 -4
  54. package/schema/schema.graphqls +1628 -30
@@ -1,8 +1,13 @@
1
+ "Incremental fragment delivery over multipart/mixed (deferSpec=20220824)."
2
+ directive @defer(if: Boolean = true, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT
3
+
1
4
  scalar File
2
5
  scalar JSON
3
6
 
4
7
  type Function {
5
8
  id: ID!
9
+ "The owning identity. A listing also includes the functions of agents the caller claimed (and, for the operator, every tenant's), so a client that must act only on its own functions keeps those whose ownerId equals whoami.userID. Returned only with a function the caller can already see."
10
+ ownerId: ID!
6
11
  name: String!
7
12
  filename: String!
8
13
  sizeBytes: Int!
@@ -11,6 +16,25 @@ type Function {
11
16
  bundled: Boolean!
12
17
  }
13
18
 
19
+ "Durable registration metadata. A published resource can still be awaiting job cleanup; settled distinguishes that case. No worker credentials or source paths are exposed."
20
+ type FunctionRegistration {
21
+ id: ID!
22
+ name: String!
23
+ createdAt: String!
24
+ "pending, published, failed, cancelled, rejected or unavailable."
25
+ state: String!
26
+ phase: String!
27
+ code: String
28
+ settled: Boolean!
29
+ canResume: Boolean!
30
+ cancelRequested: Boolean!
31
+ }
32
+
33
+ type FunctionRegistrationPage {
34
+ items: [FunctionRegistration!]!
35
+ nextCursor: ID
36
+ }
37
+
14
38
  enum DeploymentKind {
15
39
  STATIC
16
40
  SERVICE
@@ -58,6 +82,7 @@ enum DeploymentEventType {
58
82
  SERVICE_VM_STARTED
59
83
  SERVICE_VM_STOPPED
60
84
  SERVICE_CRON_TRIGGERED
85
+ SERVICE_VM_CRASHED
61
86
  }
62
87
 
63
88
  enum DeploymentEventSeverity {
@@ -67,23 +92,84 @@ enum DeploymentEventSeverity {
67
92
  }
68
93
 
69
94
  type Workspace {
95
+ "Write-only secrets are returned with null values, as in workspaceEnv."
96
+ environment: [WorkspaceEnvVar!]
97
+ "Value-free connections shared with this workspace's project."
98
+ sharedConnections: [IntegrationConnection!]
70
99
  id: ID!
71
100
  name: String!
72
101
  "Who owns this source. The listing includes the sources of agents the caller claimed, so this is how a client tells its own from an agent's: it matches one of whoami.linkedAgents[].agentID for the latter. Operations that require a DIRECTLY owned source — alias adoption targets — must filter on it."
73
102
  ownerId: ID!
74
103
  "The owner-defined project this app is grouped under (dashboard organization only); null when ungrouped."
75
104
  projectId: ID
105
+ "The containing project, when visible to the caller; null when ungrouped."
106
+ project: Project
76
107
  gitSource: GitSource
108
+ "Platform git for this app; null where the installation runs no platform git."
109
+ platformGit: WorkspacePlatformGit
110
+ "Every environment of this workspace's git repo — the main app first — including this workspace. Empty when the workspace is not git-linked."
111
+ repoEnvironments: [RepoEnvironment!]!
112
+ "The apps this workspace serves right now, by alias: everything deleteWorkspace withDeployments would take offline, and the exact list its confirmAliases must name. Domains are listed for the app's owner only."
113
+ liveApps: [LiveApp!]!
114
+ "Apps deleted with deleteApp while the workspace is git-linked. Pushes and syncs skip them while unfazed.json apps[] still declares them; removing them from apps[] clears this, and syncRepo restoreApps brings one back."
115
+ deletedApps: [DeletedApp!]!
77
116
  createdAt: String!
78
117
  updatedAt: String!
79
118
  }
80
119
 
120
+ "An app (alias) a workspace or an API client serves right now, with the custom domains routing to it."
121
+ type LiveApp {
122
+ alias: String!
123
+ "The deployment serving the alias."
124
+ deploymentID: ID!
125
+ "Custom domains that stop serving with the app; empty for a caller who does not own it."
126
+ domains: [String!]!
127
+ }
128
+
129
+ "An app deleted with deleteApp from a git-linked workspace (Workspace.deletedApps)."
130
+ type DeletedApp {
131
+ alias: String!
132
+ deletedAt: String!
133
+ "The newest commit whose import skipped the app because unfazed.json apps[] still declared it; null until one did."
134
+ skippedCommit: String
135
+ skippedAt: String
136
+ }
137
+
138
+ "An app's platform git, as the caller sees it."
139
+ type WorkspacePlatformGit {
140
+ "The git server, e.g. https://git.unfz.to. A person signs git in to it once with <url>/install.sh."
141
+ url: String!
142
+ "The app's repository by alias, e.g. https://git.unfz.to/<alias>.git, while the app deploys from it; null otherwise."
143
+ cloneURL: String
144
+ "Whether the caller may turn it on (enablePlatformGit): the app's owner and the platform operator. Team members may not, though they clone and push once it is on."
145
+ canEnable: Boolean!
146
+ "Whether apps without a repository get platform git by themselves at their next change (a file write or a deploy) once they serve."
147
+ byDefault: Boolean!
148
+ "Why this app does not get platform git by itself (its owner turned it off, or what enablePlatformGit would refuse); null when it will, or has it."
149
+ offReason: String
150
+ }
151
+
152
+ "One environment of a git-linked repo: the main app, or a sibling workspace that deploys another branch to its own aliases."
153
+ type RepoEnvironment {
154
+ workspace: Workspace!
155
+ "The environment name, e.g. staging; null for the main app."
156
+ name: String
157
+ branch: String!
158
+ lastImportStatus: String!
159
+ "Where the environment is opened: its serving app, else its latest deployment attempt; null before the first one exists."
160
+ deployment: Deployment
161
+ }
162
+
81
163
  "A project groups an owner's apps under a human-chosen name. Purely a dashboard organization layer: it does not affect app URLs or aliases, and is never exposed to agents."
82
164
  type Project {
83
165
  id: ID!
84
166
  name: String!
85
- "The owning identity (github:<login>); the dashboard uses it to tell own projects from team-shared ones."
167
+ "The owning identity. Visibility through a team does not grant ownership."
86
168
  owner: String!
169
+ "Display name of the owning human account, resolving a linked agent to its claimer. Unclaimed agents display as Unclaimed account."
170
+ ownerName: String!
171
+ "Whether the caller may rename, delete or share this project."
172
+ canManage: Boolean!
87
173
  createdAt: String!
88
174
  updatedAt: String!
89
175
  }
@@ -136,13 +222,14 @@ type TeamSharedProject {
136
222
 
137
223
  type TeamMember {
138
224
  userID: String!
139
- "Bare github login for display."
225
+ "The member's handle (a GitHub login, a display name, or the words Google account), never an email address (docs/access-control.md)."
140
226
  name: String!
141
227
  role: String!
142
228
  addedAt: String!
143
229
  }
144
230
 
145
231
  type TeamInvite {
232
+ "Stable opaque recipient key for cancellation. An email invitation key is not an account ID, even after sign-in."
146
233
  userID: String!
147
234
  name: String!
148
235
  invitedBy: String!
@@ -154,7 +241,7 @@ type TeamEvent {
154
241
  at: String!
155
242
  actor: String!
156
243
  actorName: String!
157
- "created | renamed | invited | invite_cancelled | invite_declined | joined | member_removed | member_left"
244
+ "created | renamed | invited | invite_bound | invite_cancelled | invite_declined | joined | member_removed | member_left"
158
245
  action: String!
159
246
  subject: String
160
247
  subjectName: String
@@ -184,12 +271,13 @@ input TeamIDInput {
184
271
 
185
272
  input TeamInviteInput {
186
273
  teamID: ID!
187
- "GitHub login to invite, with or without a leading @."
274
+ "GitHub login (optional leading @) or Gmail/Google Workspace email. Google invitations appear after the recipient signs in with that verified address; no email is sent."
188
275
  login: String!
189
276
  }
190
277
 
191
278
  input TeamMemberInput {
192
279
  teamID: ID!
280
+ "Account ID for member operations; use TeamInvite.userID unchanged when cancelling an invitation."
193
281
  userID: String!
194
282
  }
195
283
 
@@ -202,13 +290,125 @@ type TeamPayload {
202
290
  team: Team!
203
291
  }
204
292
 
205
- "A BYO custom domain attached to an app's alias. The edge issues its TLS cert (Let's Encrypt) once active; requests to the domain serve the app."
293
+ "A BYO custom domain attached to an app's alias. The edge issues its TLS cert once active; requests to the domain serve the app."
206
294
  type CustomDomain {
207
295
  domain: String!
208
296
  alias: String!
209
297
  "True once the platform allows the edge to issue a cert and route the domain."
210
298
  active: Boolean!
211
299
  createdAt: String!
300
+ "The DNS records to create for this domain: where to point it, plus any verification record the edge still asks for. A record with recommended=false is an alternative to a recommended one, not an addition."
301
+ dnsRecords: [DNSRecord!]!
302
+ "The domain's hostname at the edge (Cloudflare for SaaS) as of the platform's last check; null while the platform does not manage one."
303
+ status: CustomDomainStatus
304
+ "How far the domain is from serving, in one word and one sentence, as of the platform's last check."
305
+ activation: CustomDomainActivation!
306
+ }
307
+
308
+ "A custom domain's activation state."
309
+ enum CustomDomainState {
310
+ "Cloudflare is verifying the domain or issuing its certificate: usually 1–5 minutes, during which it answers the domain with \"error code: 1014\". Nothing to do unless it lasts longer than 15 minutes."
311
+ ACTIVATING
312
+ "Cloudflare has not seen the domain point at the platform for longer than activation takes, or it stopped pointing there: create or fix its CNAME."
313
+ DNS_MISSING
314
+ "Served over HTTPS."
315
+ ACTIVE
316
+ "Refused, timed out, or still not active long after activation should have finished; the message says why."
317
+ FAILED
318
+ }
319
+
320
+ "How far a custom domain is from serving."
321
+ type CustomDomainActivation {
322
+ state: CustomDomainState!
323
+ "One sentence for a person or an agent: what is happening, and what to do if anything."
324
+ message: String!
325
+ "When the current activation step started (RFC 3339); null once active or when unknown."
326
+ since: String
327
+ }
328
+
329
+ "How far a custom domain is from serving over HTTPS at the edge."
330
+ type CustomDomainStatus {
331
+ "The edge's hostname status, e.g. pending, active, blocked; \"registering\" before the hostname exists."
332
+ status: String!
333
+ "The certificate's status, e.g. initializing, pending_validation, pending_issuance, active; empty before the hostname exists."
334
+ sslStatus: String!
335
+ "True once the hostname and its certificate are both active."
336
+ live: Boolean!
337
+ "Why the domain is not live yet: verification and certificate validation errors from the edge, and the platform's own when it could not register the domain."
338
+ errors: [String!]!
339
+ }
340
+
341
+ """
342
+ A hostname of the caller's that serves an app besides its own <alias> URL:
343
+ a custom domain attached to the app's alias, or the domain of a site an
344
+ integration publishes the app to.
345
+ """
346
+ type AppDomain {
347
+ "The hostname, e.g. app.example.com."
348
+ domain: String!
349
+ "Serves the app over HTTPS now."
350
+ live: Boolean!
351
+ "How far the domain is from serving (a published site's domain is ACTIVE)."
352
+ state: CustomDomainState!
353
+ "What a domain that is not live yet waits for; null once it is live."
354
+ pending: AppDomainPending @deprecated(reason: "Use state.")
355
+ }
356
+
357
+ enum AppDomainPending {
358
+ "Its DNS: the records its owner creates at their DNS provider (Configuration → Domains lists them)."
359
+ DNS
360
+ "Its certificate, being issued now that the DNS resolves."
361
+ CERTIFICATE
362
+ }
363
+
364
+ "A DNS record a custom domain's owner creates at their DNS provider."
365
+ type DNSRecord {
366
+ "Record type: CNAME, A, or TXT."
367
+ type: String!
368
+ "The record's hostname: the custom domain itself, or a verification name under it."
369
+ name: String!
370
+ "What the record points at, or the TXT value."
371
+ content: String!
372
+ "True when the record must be proxied by Cloudflare; false when it need not be (the note says whether it must be DNS only)."
373
+ proxied: Boolean!
374
+ "True for a record to create; false for an alternative to it."
375
+ recommended: Boolean!
376
+ "When this record applies."
377
+ note: String!
378
+ }
379
+
380
+ """
381
+ A provider a person can sign in with, as their account settings show it. The
382
+ dashboard renders the list as given; it knows no provider by name. Linking
383
+ and unlinking are same-origin form posts to linkURL and unlinkURL.
384
+ """
385
+ type SignInMethod {
386
+ provider: String!
387
+ label: String!
388
+ iconURL: String!
389
+ "Whether a sign-in from this provider reaches the account."
390
+ linked: Boolean!
391
+ "The linked login or address, as the provider last reported it."
392
+ handle: String
393
+ "Whether unlinking is offered: never for the account's only sign-in."
394
+ canUnlink: Boolean!
395
+ linkURL: String!
396
+ unlinkURL: String!
397
+ }
398
+
399
+ """
400
+ A computer whose git is signed in to platform git with this account
401
+ (git-credential-unfazed, approved in the browser). Signing it out is a
402
+ same-origin form post to signOutURL.
403
+ """
404
+ type GitSignIn {
405
+ id: ID!
406
+ "What the computer called itself when it asked; not verified."
407
+ name: String!
408
+ createdAt: String!
409
+ "When it last renewed its git token (about hourly while git is used)."
410
+ lastUsedAt: String!
411
+ signOutURL: String!
212
412
  }
213
413
 
214
414
  "A public key that may authenticate to the box SSH gateway. Public keys are not secrets, so the fingerprint and type are shown in full."
@@ -244,6 +444,19 @@ input RemoveCustomDomainInput {
244
444
  domain: String!
245
445
  }
246
446
 
447
+ input MoveCustomDomainInput {
448
+ domain: String!
449
+ "The alias of the app the domain serves from now on."
450
+ alias: String!
451
+ }
452
+
453
+ "An app a custom domain may move to (moveCustomDomain)."
454
+ type CustomDomainMoveTarget {
455
+ alias: String!
456
+ "The app's name."
457
+ name: String!
458
+ }
459
+
247
460
  type GitSource {
248
461
  provider: String!
249
462
  repo: String!
@@ -259,8 +472,10 @@ type GitSource {
259
472
  lastImportError: String
260
473
  webhookURL: String
261
474
  webhookSecret: String
262
- "When set, the auto-deploy webhook has been registered on the GitHub repo (RFC3339)."
475
+ "When set, the auto-deploy webhook has been registered on the source repo (RFC3339)."
263
476
  webhookInstalledAt: String
477
+ "The environment this link deploys (e.g. staging); null for the repo's main app."
478
+ environment: String
264
479
  }
265
480
 
266
481
  type GitHubTokenStatus {
@@ -339,6 +554,22 @@ type AppEnvScope {
339
554
  }
340
555
 
341
556
  type Deployment {
557
+ "Whether the caller may create a guest feedback link for this app."
558
+ canShareReview: Boolean!
559
+ "Whether the app keeps its own platform git history (appCommits); false while platform git is off and for apps linked to another repository."
560
+ gitHistory: Boolean!
561
+ "The caller's custom hostnames serving this app (AppDomain), custom domains oldest first, then published sites'. Empty for a release that does not hold its alias. Custom domains are the app owner's: team members see none of them."
562
+ domains: [AppDomain!]!
563
+ workspace: Workspace
564
+ "Runs visible to the caller in this deployment's workspace."
565
+ runs(limit: Int = 20, offset: Int = 0): RunPage
566
+ "Effective environment for this app alias; secret values stay write-only."
567
+ environment: [WorkspaceEnvVar!]
568
+ releases: [DeploymentRelease!]
569
+ events(limit: Int = 20, offset: Int = 0): DeploymentEventPage
570
+ "Null for deployments that are not services. Errors stay within this field."
571
+ runtimeStatus: ServiceRuntimeStatus
572
+ runtimeLogs(tailBytes: Int = 16384): ServiceRuntimeLogs
342
573
  id: ID!
343
574
  workspaceID: ID!
344
575
  kind: DeploymentKind!
@@ -375,6 +606,20 @@ type Deployment {
375
606
  worker: DeploymentWorker
376
607
  }
377
608
 
609
+ "A release summary. History cannot recursively expand the same history."
610
+ type DeploymentRelease {
611
+ id: ID!
612
+ aliasHeld: Boolean!
613
+ aliasRevision: String!
614
+ status: DeploymentStatus!
615
+ error: String
616
+ serviceMode: ServiceMode!
617
+ previewUrl: String!
618
+ gitCommit: String
619
+ size: String!
620
+ createdAt: String!
621
+ }
622
+
378
623
  "The authoritative runtime target assigned to a current deployment."
379
624
  type DeploymentWorker {
380
625
  id: String!
@@ -412,6 +657,42 @@ type ServiceRuntimeStatus {
412
657
  lastStoppedAt: String
413
658
  restartCount: Int!
414
659
  lastError: String
660
+ """
661
+ The app's recent crashes on its server: its VM stopped without being asked
662
+ to. Null when it has had none since that server's runtime last started.
663
+ """
664
+ crashes: ServiceCrashes
665
+ "True when the app's server tracks its crashes, so a null crashes means none; false for a server runtime that predates crash tracking, and for boxes."
666
+ crashesTracked: Boolean!
667
+ }
668
+
669
+ "Recent crashes of a service on the server that runs it."
670
+ type ServiceCrashes {
671
+ "At least 3 crashes within the last hour."
672
+ looping: Boolean!
673
+ lastHour: Int!
674
+ last24h: Int!
675
+ "Crashes since the server's runtime last started."
676
+ total: Int!
677
+ "Newest first, at most 5; only the newest carries logTail."
678
+ recent: [ServiceCrash!]!
679
+ }
680
+
681
+ "One unexpected stop of a service's VM."
682
+ type ServiceCrash {
683
+ at: String!
684
+ vmID: String
685
+ "running (the VM was serving) or starting (the app failed before it was ready)."
686
+ phase: String!
687
+ "app_exit, app_signal, out_of_memory, kernel_panic, guest_stopped, unknown, health_check, host_killed or vm_error."
688
+ cause: String!
689
+ "The app process's exit status (128+signal when a signal ended it), when its VM reported it."
690
+ exitCode: Int
691
+ signal: String
692
+ "One line saying what happened."
693
+ message: String!
694
+ "The last lines of the VM's console before it stopped (the app's output, secrets redacted)."
695
+ logTail: String
415
696
  }
416
697
 
417
698
  type ServiceRuntimeLogs {
@@ -445,6 +726,8 @@ type IntegrationKind {
445
726
  kind: String!
446
727
  label: String!
447
728
  iconURL: String
729
+ "Lowercase catalog grouping labels: ai, database, observability, …"
730
+ tags: [String!]!
448
731
  # who supplies the backend: "platform" (unfazed runs it) or "user" (BYO)
449
732
  provenance: String!
450
733
  # how the owner connects: "none" (no connection), "fields" (a form), "link" (an auth flow)
@@ -458,7 +741,55 @@ type IntegrationKind {
458
741
  "Safe isolation mode selected when bindIntegration omits mode; null for providers without app runtime."
459
742
  defaultMode: String
460
743
  runtime: IntegrationRuntime!
744
+ "Tools and skills this provider contributes to a bound app's bot tasks. No credentials or server endpoints."
745
+ agent: IntegrationAgent!
461
746
  surfaces: [IntegrationSurface!]!
747
+ "True when the provider can read what one scoped binding's per-app resource consumed (integrationBindingUsage)."
748
+ reportsUsage: Boolean!
749
+ "Choices the owner makes when binding this kind to an app (bindIntegration options), e.g. what a git environment's database starts with. Empty for most kinds."
750
+ bindOptions: [IntegrationBindOption!]!
751
+ }
752
+ "A provider-declared choice made once, at bind time, about how the app's resource is created. A resource that already exists is kept whatever the choice."
753
+ type IntegrationBindOption {
754
+ name: String!
755
+ label: String!
756
+ description: String!
757
+ "Which apps the option exists for: app (every app) or environment (only a git environment of another app). Elsewhere bindIntegration refuses it."
758
+ appliesTo: String!
759
+ "The value recorded when bindIntegration omits the option."
760
+ default: String!
761
+ choices: [IntegrationBindChoice!]!
762
+ }
763
+ type IntegrationBindChoice {
764
+ value: String!
765
+ label: String!
766
+ description: String!
767
+ "What the owner must know before choosing this value (e.g. that it copies production data); null when nothing."
768
+ warning: String
769
+ }
770
+ "One bind option value of a binding."
771
+ type IntegrationBindOptionValue {
772
+ name: String!
773
+ value: String!
774
+ "True when the binding predates the option: nothing was chosen or recorded, and value is what such bindings were created with."
775
+ legacy: Boolean!
776
+ }
777
+ input IntegrationBindOptionInput {
778
+ name: String!
779
+ value: String!
780
+ }
781
+ type IntegrationAgent {
782
+ revision: String!
783
+ servers: [IntegrationAgentServer!]!
784
+ skills: [String!]!
785
+ }
786
+ type IntegrationAgentServer {
787
+ name: String!
788
+ description: String!
789
+ transport: String!
790
+ auth: String!
791
+ readTools: [String!]!
792
+ writeTools: [String!]!
462
793
  }
463
794
  type IntegrationSurface {
464
795
  id: String!
@@ -501,6 +832,18 @@ type IntegrationConnection {
501
832
  grantedProjectIDs: [ID!]!
502
833
  createdAt: String!
503
834
  updatedAt: String!
835
+ "OAuth state of the kind's MCP server (null for kinds without one): runs get the server only while it is active."
836
+ authorization: IntegrationConnectionAuthorization
837
+ }
838
+ "The owner's OAuth grant for a connection's MCP server. The platform is the OAuth client; tokens stay on the control plane."
839
+ type IntegrationConnectionAuthorization {
840
+ "required (no usable grant), active, or failed (the server refused a refresh; authorize again)"
841
+ status: String!
842
+ "The protected resource: the MCP endpoint the grant is for."
843
+ resource: String!
844
+ scopes: [String!]!
845
+ authorizedAt: String
846
+ error: String
504
847
  }
505
848
  type IntegrationFieldValue {
506
849
  name: String!
@@ -524,17 +867,59 @@ type IntegrationBinding {
524
867
  status: String!
525
868
  "Set when the binding rides a GRANTED connection: the lender's identity. The lender can always see and sever such bindings."
526
869
  connectionOwner: String
870
+ "True when integrationBindingUsage can answer for this binding: an active scoped binding of a usage-reporting kind. Passthrough bindings never report usage — a shared credential's spend belongs to no one app."
871
+ reportsUsage: Boolean!
872
+ "The binding's bind options: chosen or defaulted at bind time, or, for a binding recorded before an option existed, the value such bindings were created with (legacy). Empty for most kinds."
873
+ options: [IntegrationBindOptionValue!]!
874
+ "Warnings of the recorded options, e.g. that the environment's database is a copy of production data with every setting stored in it. Empty for most bindings."
875
+ warnings: [String!]!
876
+ "True when the signed-in caller can mint this binding's one-time .env reveal now (revealIntegrationEnv): the binding is active and carries credentials, the caller owns the app, and no reveal was ever issued for it (bindings older than 30 days never qualify)."
877
+ revealAvailable: Boolean!
878
+ }
879
+ "What one binding's per-app resource consumed, read from the provider's own accounting and cached for about a minute. Figures, labels and units are the provider's and are shown as-is."
880
+ type IntegrationUsage {
881
+ bindingID: ID!
882
+ kind: String!
883
+ "Start of the window the figures cover (RFC3339); null means since the resource was created."
884
+ periodStart: String
885
+ "End of the window (RFC3339); null means now."
886
+ periodEnd: String
887
+ metrics: [IntegrationUsageMetric!]!
888
+ "Optional https link to this app's resource in the provider's console."
889
+ consoleURL: String
890
+ "When unfazed read these figures from the provider (RFC3339)."
891
+ fetchedAt: String!
892
+ "Why the figures are unavailable (the provider failed, timed out or reported malformed data); null on success. Never carries credential values."
893
+ error: String
894
+ }
895
+ type IntegrationUsageMetric {
896
+ "Stable machine name, e.g. compute_hours."
897
+ name: String!
898
+ label: String!
899
+ value: Float!
900
+ "Display unit; may be empty for plain counts."
901
+ unit: String!
902
+ "Optional cap configured for this app, in the same unit."
903
+ limit: Float
527
904
  }
528
905
  type IntegrationConnectionPayload {
529
906
  connection: IntegrationConnection!
530
907
  }
531
908
  type IntegrationBindingPayload {
532
909
  binding: IntegrationBinding!
533
- "One-time command (curl) that prints THIS binding's freshly minted credentials as .env lines, exactly once. Present only in the bind response for credential-carrying bindings; it can never be re-issued — only its hash is stored."
910
+ "One-time command (curl) that prints THIS binding's credentials as .env lines, exactly once. Present in the bind response for an active credential-carrying binding whose single reveal was not issued yet; a binding that becomes active later gets it once from revealIntegrationEnv. Never issued twice — only its hash is stored."
534
911
  revealCommand: String
535
912
  "RFC3339 time the unused reveal stops working."
536
913
  revealExpiresAt: String
537
914
  }
915
+ "The single one-time .env reveal of one binding, minted by revealIntegrationEnv."
916
+ type IntegrationEnvReveal {
917
+ binding: IntegrationBinding!
918
+ "One-time command (curl) that prints THIS binding's credentials as .env lines. Whoever runs it consumes it; it is never issued again for this binding."
919
+ revealCommand: String!
920
+ "RFC3339 time the unused reveal stops working."
921
+ revealExpiresAt: String!
922
+ }
538
923
  "Account-level non-secret event routing. Credential/runtime grants never use this type."
539
924
  type IntegrationSubscription {
540
925
  id: ID!
@@ -609,6 +994,142 @@ input BindIntegrationInput {
609
994
  alias: String!
610
995
  "Optional provider-supported isolation mode. Omit to use the provider's safe default (scoped for Postgres)."
611
996
  mode: String
997
+ "Choices for the kind's bindOptions, by name. Omitted options take their default; an option the app does not take (e.g. an environment-only option on a main app) is refused."
998
+ options: [IntegrationBindOptionInput!]
999
+ }
1000
+ "The account's chosen model-provider connection for one harness (docs/run-design.md phase 12 step 2). connectionName and kind are omitted when the referenced connection was since deleted."
1001
+ type RunHarnessSetting {
1002
+ harness: String!
1003
+ connectionID: ID!
1004
+ connectionName: String
1005
+ kind: String
1006
+ model: String
1007
+ updatedAt: String!
1008
+ }
1009
+ type RunHarnessSettingPayload {
1010
+ setting: RunHarnessSetting!
1011
+ }
1012
+ "A named run bundle (docs/run-design.md phase 12 step 3): base harness, optional provider connection + model, extra env. A preset picks a CONNECTION, never carries a key; an env value may reference a workspace name as \"$NAME\", resolved when a run is created."
1013
+ type RunPreset {
1014
+ name: String!
1015
+ description: String!
1016
+ harness: String!
1017
+ connectionID: ID
1018
+ "Set when the referenced connection still exists: its display name and kind."
1019
+ connectionName: String
1020
+ kind: String
1021
+ model: String
1022
+ env: [RunPresetEnv!]!
1023
+ createdAt: String!
1024
+ updatedAt: String!
1025
+ }
1026
+ type RunPresetEnv {
1027
+ name: String!
1028
+ value: String!
1029
+ }
1030
+ type RunPresetPayload {
1031
+ preset: RunPreset!
1032
+ }
1033
+ input RunPresetEnvInput {
1034
+ name: String!
1035
+ value: String!
1036
+ }
1037
+ input SaveRunPresetInput {
1038
+ name: String!
1039
+ description: String
1040
+ harness: String!
1041
+ connectionID: ID
1042
+ model: String
1043
+ env: [RunPresetEnvInput!]
1044
+ }
1045
+ input DeleteRunPresetInput {
1046
+ name: String!
1047
+ }
1048
+ "An account-level run trigger (docs/run-design.md phase 13): an inbound webhook event becomes an agent run. The secret authenticates deliveries; it is returned only by runTriggerSecret."
1049
+ type RunTrigger {
1050
+ id: ID!
1051
+ name: String!
1052
+ enabled: Boolean!
1053
+ "\"webhook\" (generic HMAC/bearer) or \"github\" (X-Hub-Signature-256)."
1054
+ source: String!
1055
+ "Event names that fire (github: X-GitHub-Event); empty fires on every event."
1056
+ filter: [String!]!
1057
+ template: RunTriggerTemplate!
1058
+ cooldownSeconds: Int!
1059
+ "Allow parallel firings; off = one at a time, later firings queue (owner decision D3)."
1060
+ allowParallel: Boolean!
1061
+ "The POST endpoint to configure at the sender: /hooks/runs/{id}."
1062
+ hookPath: String!
1063
+ "Recent firings, newest first."
1064
+ firings: [RunTriggerFiring!]!
1065
+ "Firings waiting for a slot (cooldown or a still-running previous firing)."
1066
+ queued: Int!
1067
+ createdAt: String!
1068
+ updatedAt: String!
1069
+ }
1070
+ type RunTriggerTemplate {
1071
+ workspaceID: ID!
1072
+ alias: String
1073
+ harness: String
1074
+ preset: String
1075
+ "May hold {{event.<dotted.path>}} placeholders, interpolated from the JSON payload."
1076
+ prompt: String!
1077
+ access: String
1078
+ maxSeconds: Int
1079
+ metadata: JSON
1080
+ }
1081
+ type RunTriggerFiring {
1082
+ id: ID!
1083
+ at: String!
1084
+ "started | queued | skipped | failed | expired"
1085
+ status: String!
1086
+ summary: String
1087
+ runID: ID
1088
+ reason: String
1089
+ }
1090
+ type RunTriggerPayload {
1091
+ trigger: RunTrigger!
1092
+ }
1093
+ input RunTriggerTemplateInput {
1094
+ workspaceID: ID!
1095
+ alias: String
1096
+ harness: String
1097
+ preset: String
1098
+ prompt: String!
1099
+ access: String
1100
+ maxSeconds: Int
1101
+ metadata: JSON
1102
+ }
1103
+ input SaveRunTriggerInput {
1104
+ "Empty creates; a set id updates that trigger."
1105
+ id: ID
1106
+ name: String!
1107
+ enabled: Boolean!
1108
+ source: String!
1109
+ "Empty keeps the existing secret (a fresh one is generated on create); set replaces it."
1110
+ secret: String
1111
+ filter: [String!]
1112
+ template: RunTriggerTemplateInput!
1113
+ cooldownSeconds: Int
1114
+ allowParallel: Boolean
1115
+ }
1116
+ input RunTriggerIDInput {
1117
+ id: ID!
1118
+ }
1119
+ input SetRunHarnessProviderInput {
1120
+ harness: String!
1121
+ connectionID: ID!
1122
+ model: String
1123
+ }
1124
+ input ClearRunHarnessProviderInput {
1125
+ harness: String!
1126
+ }
1127
+ "One of the caller's own connections eligible to back a harness: its kind implements harness credentials and actually serves this one."
1128
+ type RunHarnessProviderChoice {
1129
+ harness: String!
1130
+ connectionID: ID!
1131
+ name: String!
1132
+ kind: String!
612
1133
  }
613
1134
  input IntegrationIDInput {
614
1135
  id: ID!
@@ -681,6 +1202,8 @@ type DeploymentEventPage {
681
1202
  type Invocation {
682
1203
  id: ID!
683
1204
  functionID: ID!
1205
+ "True while the original invocation is unfinished. Query functionInvocation with this ID; do not start a new invocation to check its outcome."
1206
+ pending: Boolean!
684
1207
  ok: Boolean!
685
1208
  result: JSON
686
1209
  stdout: String!
@@ -696,7 +1219,7 @@ enum CurrentActorKind {
696
1219
  OWNER
697
1220
  "An automated caller using a self-registered bearer token. Subject to free-tier usage/resource limits; a user can claim it to link it to their account."
698
1221
  AGENT
699
- "A human signed in via GitHub OAuth, who manages their linked agents and apps."
1222
+ "A human signed in via an enabled identity provider, who manages their linked agents and apps."
700
1223
  USER
701
1224
  }
702
1225
 
@@ -714,20 +1237,115 @@ type CurrentActorResources {
714
1237
  }
715
1238
 
716
1239
  """
717
- Per-owner compute usage for the current calendar month (docs/run-design.md,
718
- "Metering (phase 0, ships alone)"): VM-seconds and MiB-seconds accrued across
719
- every VM the runner has booted for this owner (service, box), keyed on the raw
720
- owner id. No pricing, no currency — a price is a presentation decision made
721
- later, on top of these raw numbers. Populated for both humans (session
722
- callers) and agents, unlike `usage`/`resources` above, which are agent-only.
1240
+ Recorded compute for the caller's raw owner ID in the current UTC calendar
1241
+ month. Linked API identities retain separate ownership and are not included.
1242
+ This is metering, not an invoice: no prices or charges are calculated. Running
1243
+ VMs may await an hourly checkpoint; builds, storage, network and provider API
1244
+ usage are not included. Null means the ledger could not be read.
723
1245
  """
724
1246
  type CurrentActorCompute {
725
1247
  seconds: Float!
726
1248
  mibSeconds: Float!
727
1249
  since: String!
728
1250
  until: String!
1251
+ "When this ledger snapshot was read, in UTC."
1252
+ generatedAt: String!
1253
+ "Latest recorded activity in the period, not a completeness watermark."
1254
+ lastRecordedAt: String
1255
+ "Corrupt, conflicting, or memory-less records make this report incomplete."
1256
+ incomplete: Boolean!
1257
+ byMode: [ComputeModeUsage!]!
729
1258
  }
730
1259
 
1260
+ type ComputeModeUsage {
1261
+ mode: String!
1262
+ seconds: Float!
1263
+ mibSeconds: Float!
1264
+ }
1265
+
1266
+ "An agent (docs/agents-design.md): a long-lived worker with a name and a job whose setup a run inherits. It owns a hidden workspace with its computer, a BOX deployment, in it."
1267
+ type Agent {
1268
+ id: ID!
1269
+ ownerId: ID!
1270
+ name: String!
1271
+ "The one-line role under the name."
1272
+ job: String!
1273
+ "Standing rules the harness is given on every run; never sent as a task."
1274
+ description: String!
1275
+ "The agent's own workspace (hidden from the apps lists): its env and secrets, the home of its computer."
1276
+ workspaceID: ID!
1277
+ "The alias of the agent's computer (its SSH user name); null until it exists."
1278
+ boxAlias: String
1279
+ "The agent's computer: the newest BOX deployment in its workspace (BUILDING while it starts, FAILED when its build failed)."
1280
+ computer: Deployment
1281
+ "The harness a run of this agent uses; null means the node's first catalogue entry."
1282
+ harness: String
1283
+ "The provider connection backing its runs; null means the account's per-harness setting."
1284
+ connectionID: ID
1285
+ "Set when the referenced connection still exists: its display name and kind."
1286
+ connectionName: String
1287
+ kind: String
1288
+ model: String
1289
+ "read-only or read-write: what a run of this agent may do."
1290
+ access: String!
1291
+ "\"human\" routes the harness's guarded actions to a person; empty keeps the harness's defaults."
1292
+ approval: String!
1293
+ "The workspaces the agent works on; the first supplies a run's files and env until runs execute on the agent's computer."
1294
+ scopeWorkspaceIDs: [ID!]!
1295
+ "Remembered \"always\" answers to permission questions of this agent's runs: a tool name, a whole MCP server (mcp__postgres), or a tool with an input prefix (Bash(git push:*))."
1296
+ allowedTools: [String!]!
1297
+ "Derived on the server from the newest run and the computer, so every surface agrees."
1298
+ state: AgentState!
1299
+ "One line about the state: the current task, the pending question, or what failed."
1300
+ stateDetail: String!
1301
+ "The agent's newest run, whoever messaged it."
1302
+ lastRun: Run
1303
+ createdAt: String!
1304
+ updatedAt: String!
1305
+ }
1306
+ "What an agent is doing: STARTING (computer being made), IDLE, WORKING (a run in flight), NEEDS_YOU (a run waits for an answer), ASLEEP (computer stopped), FAILED (the computer or the last run)."
1307
+ enum AgentState {
1308
+ STARTING
1309
+ IDLE
1310
+ WORKING
1311
+ NEEDS_YOU
1312
+ ASLEEP
1313
+ FAILED
1314
+ }
1315
+ type AgentPayload {
1316
+ agent: Agent!
1317
+ }
1318
+ input CreateAgentInput {
1319
+ name: String!
1320
+ job: String
1321
+ description: String
1322
+ harness: String
1323
+ connectionID: ID
1324
+ model: String
1325
+ access: String
1326
+ approval: String
1327
+ scopeWorkspaceIDs: [ID!]
1328
+ "The computer's size; omitted uses the platform defaults."
1329
+ boxResources: BoxResourcesInput
1330
+ }
1331
+ input UpdateAgentInput {
1332
+ id: ID!
1333
+ name: String
1334
+ job: String
1335
+ description: String
1336
+ harness: String
1337
+ connectionID: ID
1338
+ model: String
1339
+ access: String
1340
+ approval: String
1341
+ "Replaces the scope when given; an empty list clears it."
1342
+ scopeWorkspaceIDs: [ID!]
1343
+ "Replaces the remembered permission rules when given; an empty list forgets them all."
1344
+ allowedTools: [String!]
1345
+ }
1346
+ input DeleteAgentInput {
1347
+ id: ID!
1348
+ }
731
1349
  enum RunStatus {
732
1350
  QUEUED
733
1351
  RUNNING
@@ -753,13 +1371,22 @@ that executed one task and was destroyed when it exited. Deployments answer
753
1371
  "what is running"; runs answer "what happened".
754
1372
  """
755
1373
  type Run {
1374
+ "Source workspace and its current alias holders, when still visible."
1375
+ source: RunSource
1376
+ events(limit: Int = 100): [RunEvent!]
1377
+ console(tailBytes: Int = 16384): RunConsole
1378
+ transcript(tailBytes: Int = 16384): RunTranscript
756
1379
  id: ID!
757
1380
  workspaceID: ID!
1381
+ "The agent this run is a task of (docs/agents-design.md); null for a plain app run."
1382
+ agentID: ID
758
1383
  ownerId: ID!
759
1384
  status: RunStatus!
760
1385
  rawStatus: String!
761
1386
  command: String
762
1387
  harness: String
1388
+ "The preset this run was created from (display-only; resolved at creation)."
1389
+ preset: String
763
1390
  prompt: String
764
1391
  setup: [String!]!
765
1392
  skills: [String!]!
@@ -800,6 +1427,19 @@ type Run {
800
1427
  settledAt: String
801
1428
  }
802
1429
 
1430
+ "A terminal source summary: run navigation cannot recursively expand runs."
1431
+ type RunSource {
1432
+ id: ID!
1433
+ name: String!
1434
+ deployments: [RunSourceDeployment!]!
1435
+ }
1436
+
1437
+ type RunSourceDeployment {
1438
+ id: ID!
1439
+ alias: String
1440
+ createdAt: String!
1441
+ }
1442
+
803
1443
  "One bound integration's contribution to a run. env is the credential set injected: empty (none), read-only or read-write."
804
1444
  type RunIntegration {
805
1445
  kind: String!
@@ -892,6 +1532,8 @@ type CurrentActorAgent {
892
1532
  claimedAt: String
893
1533
  "Deployments owned by this linked agent. Resolved lazily — only fetched when selected."
894
1534
  deployments: [Deployment!]!
1535
+ "Every app this agent serves right now, across its workspaces: what deleteLinkedAgent takes offline, and the exact list its confirmAliases must name."
1536
+ liveApps: [LiveApp!]!
895
1537
  }
896
1538
 
897
1539
  type CurrentActor {
@@ -914,11 +1556,160 @@ type CurrentActor {
914
1556
  linkedAgents: [CurrentActorAgent!]!
915
1557
  }
916
1558
 
1559
+ "The authenticated account. Nested fields can be delivered independently with @defer."
1560
+ type Viewer {
1561
+ availableIntegrations: [IntegrationKind!]
1562
+ integrationConnections: [IntegrationConnection!]
1563
+ integrationSubscriptions: [IntegrationSubscription!]
1564
+ integrationBindings: [IntegrationBinding!]
1565
+ customDomains: [CustomDomain!]
1566
+ }
1567
+
1568
+
1569
+ "Current app placement without assessing destinations or granting placement authority."
1570
+ type PlacementDisplay {
1571
+ current: PlacementCurrent
1572
+ target: PlacementServer
1573
+ blockers: [PlacementBlocker!]!
1574
+ }
1575
+ "Public metadata for the exact server currently assigned to the app."
1576
+ type PlacementServer {
1577
+ id: ID!
1578
+ "CONTROL_PLANE or WORKER."
1579
+ kind: String!
1580
+ name: String!
1581
+ "Empty when the server has no configured region."
1582
+ region: String!
1583
+ incarnation: String!
1584
+ }
1585
+
1586
+ "Read-only placement metadata. Blockers are observations, never permission to move data."
1587
+ type PlacementCatalog {
1588
+ current: PlacementCurrent
1589
+ targets: [PlacementTarget!]!
1590
+ "Regions advertised by the caller's targets, before the optional region filter."
1591
+ regions: [String!]!
1592
+ blockers: [PlacementBlocker!]!
1593
+ "Where a new app would land. Set only when no current app is selected."
1594
+ newApps: PlacementDecision
1595
+ }
1596
+ "Where a new app lands. The first release starts on the control plane; a WORKER choice then moves the app there with its App State through the ordinary placement move. Existing apps keep their server."
1597
+ type PlacementDecision {
1598
+ serverID: String!
1599
+ serverName: String!
1600
+ "CONTROL_PLANE or WORKER."
1601
+ kind: String!
1602
+ region: String!
1603
+ "request, account_default, default or platform."
1604
+ source: String!
1605
+ "The server that was asked for when the app cannot use it; empty otherwise."
1606
+ requested: String!
1607
+ "Why the app stays on the control plane, e.g. worker_disconnected or static_on_control_plane; empty when it lands as chosen."
1608
+ code: String!
1609
+ message: String!
1610
+ }
1611
+ type PlacementTarget {
1612
+ id: ID!
1613
+ "CONTROL_PLANE or WORKER."
1614
+ kind: String!
1615
+ name: String!
1616
+ "Empty when the server has no configured region."
1617
+ region: String!
1618
+ "Exact installation identity; empty when it is unknown for the default server."
1619
+ incarnation: String!
1620
+ status: String!
1621
+ connected: Boolean!
1622
+ "An authenticated session on a settled runtime release; not a data-admission grant."
1623
+ ready: Boolean!
1624
+ deployBlockers: [PlacementBlocker!]!
1625
+ moveBlockers: [PlacementBlocker!]!
1626
+ }
1627
+ type PlacementBlocker { code: String!, message: String! }
1628
+ type PlacementCurrent {
1629
+ deploymentID: ID!
1630
+ alias: String!
1631
+ aliasRevision: String!
1632
+ "Empty if the exact current placement is unknown."
1633
+ targetID: String!
1634
+ incarnation: String!
1635
+ state: String!
1636
+ blockers: [PlacementBlocker!]!
1637
+ }
1638
+ input PlacementTargetsInput {
1639
+ workspaceID: ID
1640
+ alias: String
1641
+ deploymentID: ID
1642
+ "Filter exact servers by region; this does not select a regional scheduler."
1643
+ region: String
1644
+ }
1645
+
1646
+ type PlacementMoveApp { deploymentID: ID!, workspaceID: ID!, alias: String! }
1647
+ type PlacementMovePlan {
1648
+ deploymentID: ID!
1649
+ "Public retry key; empty for an older operator-created move."
1650
+ idempotencyKey: String!
1651
+ planDigest: String!
1652
+ targetID: String!
1653
+ targetIncarnation: String!
1654
+ affectedApps: [PlacementMoveApp!]!
1655
+ resourceCount: Int!
1656
+ }
1657
+ type PlacementMove {
1658
+ id: ID!
1659
+ phase: String!
1660
+ complete: Boolean!
1661
+ running: Boolean!
1662
+ resumable: Boolean!
1663
+ plan: PlacementMovePlan!
1664
+ }
1665
+ input PlacementMovePlanInput {
1666
+ deploymentID: ID!
1667
+ targetID: String!
1668
+ targetIncarnation: String!
1669
+ idempotencyKey: String!
1670
+ }
1671
+ input BeginPlacementMoveInput {
1672
+ deploymentID: ID!
1673
+ targetID: String!
1674
+ targetIncarnation: String!
1675
+ idempotencyKey: String!
1676
+ planDigest: String!
1677
+ }
1678
+ input PlacementMoveStatusInput {
1679
+ deploymentID: ID!
1680
+ idempotencyKey: String
1681
+ moveID: ID
1682
+ }
1683
+ input RunPlacementMoveInput {
1684
+ deploymentID: ID!
1685
+ idempotencyKey: String
1686
+ moveID: ID!
1687
+ }
1688
+
917
1689
  type Query {
1690
+ "Read-only exact server-move plan for a current app. Ordinary apps need no operator enrollment: App State follows compute and external connections stay unchanged. Existing managed-data pilots retain their shared-data group. Does not reserve or start a move."
1691
+ placementMovePlan(input: PlacementMovePlanInput!): PlacementMovePlan!
1692
+ "Saved move by key/ID; with neither selector, the app's incomplete move. Null is absence, never evidence that a timed-out operation rolled back."
1693
+ placementMove(input: PlacementMoveStatusInput!): PlacementMove
1694
+
1695
+ "Authoritative current server for a visible app, without assessing destination eligibility."
1696
+ deploymentPlacement(deploymentID: ID!): PlacementDisplay
1697
+ "Servers for the caller or an owned app/workspace. Shared-project access alone does not expose another owner's servers. Historical releases have no current placement."
1698
+ placementTargets(input: PlacementTargetsInput): PlacementCatalog!
1699
+ "Where a new app would land for the caller, or for the owner of an owned workspace. Read-only."
1700
+ newAppPlacement(workspaceID: ID): PlacementDecision!
1701
+
1702
+ viewer: Viewer
918
1703
  whoami: CurrentActor!
919
1704
  functions: [Function!]!
920
1705
  functionsPage(input: PageInput): FunctionPage!
921
1706
  function(id: ID!): Function
1707
+ "Read the caller's saved invocation without executing it again. Requires deploy scope, as invoking does."
1708
+ functionInvocation(id: ID!): Invocation!
1709
+ "Read one visible registration without contacting a worker or starting work."
1710
+ functionRegistration(id: ID!): FunctionRegistration!
1711
+ "Discover saved registrations, including a lost initial response. Ordered by immutable ID; limit 1–100."
1712
+ functionRegistrations(after: ID, limit: Int = 20): FunctionRegistrationPage!
922
1713
  "Sources visible to the current account: directly owned plus those of linked claimed agents for a user; own for an agent; all for the operator. The same scope as deployments, so an agent's apps can be grouped into the caller's projects."
923
1714
  workspaces: [Workspace!]!
924
1715
  "Paginated sources visible to the current account, with the same ownership scope as workspaces."
@@ -932,8 +1723,14 @@ type Query {
932
1723
  myTeamInvites: [TeamInviteForMe!]!
933
1724
  "The signed-in owner's BYO custom domains, oldest first."
934
1725
  customDomains: [CustomDomain!]!
1726
+ "The apps a custom domain of yours may move to (moveCustomDomain), by alias."
1727
+ customDomainMoveTargets(domain: String!): [CustomDomainMoveTarget!]!
935
1728
  "The signed-in account's SSH public keys — the keys that may open its boxes."
936
1729
  sshKeys: [SSHKey!]!
1730
+ "The sign-in providers the platform offers, and which of them are linked to the signed-in account."
1731
+ signInMethods: [SignInMethod!]!
1732
+ "Computers whose git is signed in to platform git with the signed-in account, newest first."
1733
+ gitSignIns: [GitSignIn!]!
937
1734
  "Integration types available to connect (postgres, redis, …)."
938
1735
  availableIntegrations: [IntegrationKind!]!
939
1736
  "The signed-in owner's connected backends."
@@ -942,6 +1739,8 @@ type Query {
942
1739
  projectSharedConnections(projectID: ID!): [IntegrationConnection!]!
943
1740
  "The signed-in owner's app bindings (which app uses which backend)."
944
1741
  integrationBindings: [IntegrationBinding!]!
1742
+ "Usage of one binding's per-app resource, read live from its provider (bounded, cached ~60s). Fails for a binding that does not report usage (see IntegrationBinding.reportsUsage); a provider failure is returned in error, not as a GraphQL error."
1743
+ integrationBindingUsage(id: ID!): IntegrationUsage!
945
1744
  "Account-level event subscriptions, including disabled state."
946
1745
  integrationSubscriptions: [IntegrationSubscription!]!
947
1746
  "Secret-free configuration effective for this app's next deploy or lifecycle event."
@@ -950,6 +1749,10 @@ type Query {
950
1749
  publishDomainProbe(bindingID: ID!): PublishDomainProbe!
951
1750
  workspaceFiles(workspaceID: ID!): [WorkspaceFile!]!
952
1751
  workspaceFilesPage(input: ListWorkspaceFilesInput!): WorkspaceFilePage!
1752
+ "History of an app with platform git: its branch's commits, newest first (limit default 20, max 100); before continues from a page's nextBefore."
1753
+ appCommits(workspaceID: ID!, limit: Int, before: String): AppCommitPage!
1754
+ "One commit of an app with platform git with its diff against its first parent."
1755
+ appCommit(workspaceID: ID!, sha: String!): AppCommitDiff!
953
1756
  readWorkspaceFile(input: ReadWorkspaceFileInput!): WorkspaceFileContent
954
1757
  workspaceEnv(workspaceID: ID!): [WorkspaceEnvVar!]!
955
1758
  workspaceEnvPage(input: ListWorkspaceEnvInput!): WorkspaceEnvPage!
@@ -974,10 +1777,25 @@ type Query {
974
1777
  deploymentEventsPage(input: ListDeploymentEventsInput!): DeploymentEventPage!
975
1778
  "The harness presets a run may name: what each is and which workspace secrets it needs. Empty when this node's box image carries no catalogue."
976
1779
  runHarnesses: [RunHarness!]!
1780
+ "The caller's chosen model-provider connection per harness. A run of that harness receives its env unless the workspace sets the same name."
1781
+ runHarnessSettings: [RunHarnessSetting!]!
1782
+ "The caller's named run presets, sorted by name."
1783
+ runPresets: [RunPreset!]!
1784
+ "The caller's run triggers, sorted by name."
1785
+ runTriggers: [RunTrigger!]!
1786
+ "One trigger's shared secret — shown to its owner to configure the sending side."
1787
+ runTriggerSecret(input: RunTriggerIDInput!): String!
1788
+ "The caller's own connections eligible to back each harness in the catalogue, computed server-side (kind implements harness credentials and actually serves that harness)."
1789
+ runHarnessProviderChoices: [RunHarnessProviderChoice!]!
1790
+ "The models a connection's provider offers for a harness, fetched live from the provider (e.g. GET /v1/models); empty when the kind cannot list models (no remote catalogue, or an oauth-only credential with no API access) — the caller falls back to free text."
1791
+ runHarnessProviderModels(harness: String!, connectionID: ID!): [String!]!
977
1792
  "One agent run visible to the caller; not-found otherwise."
978
1793
  run(id: ID!): Run!
979
1794
  "The caller's agent runs, newest first."
980
1795
  runs(input: ListRunsInput): RunPage!
1796
+ "Agents the caller may see (docs/agents-design.md): its own and those of identities it claimed; a claimed identity sees only its own, never its claimer's. Oldest first."
1797
+ agents: [Agent!]!
1798
+ agent(id: ID!): Agent!
981
1799
  "A run's own history, oldest first (at most limit, default 100)."
982
1800
  runEvents(runID: ID!, limit: Int): [RunEvent!]!
983
1801
  "The live console tail of a RUNNING run (tailBytes, default 16384)."
@@ -994,8 +1812,19 @@ type Query {
994
1812
  }
995
1813
 
996
1814
  type Mutation {
1815
+ "Persist the exact previewed move before execution. Retry with the same key/digest after a lost response."
1816
+ beginPlacementMove(input: BeginPlacementMoveInput!): PlacementMove!
1817
+ "Execute or resume the saved move. On interruption, read status and retry this same ID; cancellation does not undo it."
1818
+ runPlacementMove(input: RunPlacementMoveInput!): PlacementMove!
1819
+
997
1820
  "Start an agent run; returns once its VM has started (RUNNING) or FAILED with error."
998
1821
  createRun(input: CreateRunInput!): Run!
1822
+ "Create an agent: the record, its own hidden workspace, and its computer (a BOX deployment, sized like any box). A name is enough; the rest defaults. Requires a claimed account, like a box."
1823
+ createAgent(input: CreateAgentInput!): AgentPayload!
1824
+ "Change an agent's setup; omitted fields stay as they are. Owner only."
1825
+ updateAgent(input: UpdateAgentInput!): AgentPayload!
1826
+ "Delete an agent: its active runs are cancelled, its computer and disk deleted, its workspace removed. The agent itself cannot be restored; its computer's files and disk are kept by the platform for 30 days for investigation, then erased. Owner only."
1827
+ deleteAgent(input: DeleteAgentInput!): DeleteResultPayload!
999
1828
  "Stop an agent run (idempotent on an already-cancelled run)."
1000
1829
  cancelRun(id: ID!): Run!
1001
1830
  "Answer a BLOCKED run's pending question; the run resumes."
@@ -1003,7 +1832,11 @@ type Mutation {
1003
1832
  "Continue a finished run: a new run in the same workspace with the parent's context prepended to the prompt."
1004
1833
  followUpRun(input: FollowUpRunInput!): Run!
1005
1834
  registerFunction(input: RegisterFunctionInput!): Function!
1835
+ "Reconcile the original registration, preserving its source and worker identity. May finalize an interrupted preparation as failed; never retries a completed failed execution. Requires deploy scope."
1836
+ resumeFunctionRegistration(id: ID!): FunctionRegistration!
1006
1837
  invokeFunction(input: InvokeFunctionInput!): Invocation!
1838
+ "Permanently delete a published function: it stops being invocable and its stored source and bundle are removed. Saved invocation results are not affected. Unpublished registrations are not deleted. Requires deploy scope. Not idempotent: once deleted, the ID reports function was not found like any unknown ID, so a client retrying its own delete may treat that error as already deleted."
1839
+ deleteFunction(input: DeleteFunctionInput!): DeleteResultPayload!
1007
1840
  createWorkspace(input: CreateWorkspaceInput!): CreateWorkspacePayload!
1008
1841
  "Rename an app (its workspace). Does not change its alias or public URL."
1009
1842
  renameWorkspace(input: RenameWorkspaceInput!): Workspace!
@@ -1041,10 +1874,28 @@ type Mutation {
1041
1874
  connectIntegration(input: ConnectIntegrationInput!): IntegrationConnectionPayload!
1042
1875
  "Start the link flow for a link integration (e.g. Telegram); returns a URL to open."
1043
1876
  startIntegrationLink(kind: String!): IntegrationLinkPayload!
1877
+ "Begin the owner's OAuth authorization of a connection's MCP server: open the returned URL in a browser signed in to this account; the callback completes it."
1878
+ startConnectionAuthorization(input: IntegrationIDInput!): IntegrationLinkPayload!
1879
+ "Choose which of the caller's own connections backs a harness; refused when the caller does not own the connection or it does not serve this harness."
1880
+ setRunHarnessProvider(input: SetRunHarnessProviderInput!): RunHarnessSettingPayload!
1881
+ "Create or replace (by name) one of the caller's run presets. A preset naming a connection is refused unless the caller owns it and it serves the preset's harness."
1882
+ saveRunPreset(input: SaveRunPresetInput!): RunPresetPayload!
1883
+ "Forget a preset by name; runs already created from it are untouched."
1884
+ deleteRunPreset(input: DeleteRunPresetInput!): DeleteResultPayload!
1885
+ "Create or update a run trigger. The template's workspace must be visible to the caller; a named preset must exist."
1886
+ saveRunTrigger(input: SaveRunTriggerInput!): RunTriggerPayload!
1887
+ "Forget a trigger and its queued firings."
1888
+ deleteRunTrigger(input: RunTriggerIDInput!): DeleteResultPayload!
1889
+ "Forget the caller's chosen provider for a harness; it falls back to no credential."
1890
+ clearRunHarnessProvider(input: ClearRunHarnessProviderInput!): DeleteResultPayload!
1891
+ "Revoke (best effort, at the server) and forget a connection's OAuth grant; runs stop getting the server."
1892
+ revokeConnectionAuthorization(input: IntegrationIDInput!): DeleteResultPayload!
1044
1893
  "Delete a connected backend by id. Active bindings must be unbound first; detached bindings retain it until rollback/provider cleanup completes."
1045
1894
  deleteIntegrationConnection(input: IntegrationIDInput!): DeleteResultPayload!
1046
1895
  "Bind a backend to ONE app. Scoped providers first create a distinct per-app resource and credential; the app receives only that credential on its next deploy."
1047
1896
  bindIntegration(input: BindIntegrationInput!): IntegrationBindingPayload!
1897
+ "Mint the single one-time .env reveal of an ACTIVE credential-carrying binding that never had one — typically a binding whose provider finished provisioning after bindIntegration returned. Only the app's owner may call it; it is refused while the binding is still provisioning and once a reveal was issued for the binding."
1898
+ revealIntegrationEnv(input: IntegrationIDInput!): IntegrationEnvReveal!
1048
1899
  "Attach a custom hostname (a zone already in the connected account) to a publish binding's site. Replaces the previously attached hostname, if any."
1049
1900
  setPublishDomain(input: PublishDomainInput!): IntegrationBindingPayload!
1050
1901
  "Detach the publish binding's custom hostname. The site stays reachable at its provider URL."
@@ -1076,6 +1927,8 @@ type Mutation {
1076
1927
  addCustomDomain(input: AddCustomDomainInput!): CustomDomain!
1077
1928
  "Detach a custom domain; the app keeps serving on its alias/URL."
1078
1929
  removeCustomDomain(input: RemoveCustomDomainInput!): DeleteResultPayload!
1930
+ "Move a custom domain to another of your apps without activating it again: its Cloudflare hostname and certificate are kept and re-pointed, so it never answers \"error code: 1014\". The previous app answers it until Cloudflare has switched, usually within seconds. Only between apps of the domain's own account."
1931
+ moveCustomDomain(input: MoveCustomDomainInput!): CustomDomain!
1079
1932
  writeWorkspaceTextFile(input: WriteWorkspaceTextFileInput!): WorkspaceFilePayload!
1080
1933
  writeWorkspaceBase64File(input: WriteWorkspaceBase64FileInput!): WorkspaceFilePayload!
1081
1934
  uploadWorkspaceFile(input: UploadWorkspaceFileInput!): WorkspaceFilePayload!
@@ -1098,31 +1951,44 @@ type Mutation {
1098
1951
  adoptDeploymentAlias(input: AdoptDeploymentAliasInput!): CreateDeploymentPayload!
1099
1952
  "Deploy a workspace's CURRENT files, detecting the kind from them (a root server.ts/index.html or an unfazed.json). The recovery path after a failed import: add/fix unfazed.json (or an entry), then deploy — no git re-fetch. Errors with what to add if the files still aren't deployable."
1100
1953
  deployWorkspace(input: DeployWorkspaceInput!): CreateDeploymentPayload!
1101
- "Import and deploy a GitHub repo into a new workspace. Kind/runtime are detected from the repo root (server.ts/go.mod+main.go -> SERVICE, index.html -> STATIC) unless a root unfazed.json overrides it. For a monorepo, a root unfazed.json `apps` array deploys SEVERAL apps from one push — each {alias, rootDir} builds that subdirectory as its own app (apps[] is repo config, not an input here; the same applies to syncRepo and webhook pushes). Returns immediately with an IMPORTING status; the fetch+build runs in the background."
1954
+ "Import and deploy a Git repo into a new workspace using the selected provider (GitHub by default). Kind/runtime are detected from the repo root (server.ts/go.mod+main.go -> SERVICE, index.html -> STATIC) unless a root unfazed.json overrides it. For a monorepo, a root unfazed.json `apps` array deploys SEVERAL apps from one push — each {alias, rootDir} builds that subdirectory as its own app (apps[] is repo config, not an input here; the same applies to syncRepo and webhook pushes). Returns immediately with an IMPORTING status; the fetch+build runs in the background."
1102
1955
  importRepo(input: ImportRepoInput!): ImportRepoPayload!
1103
- "Link a GitHub repo to an EXISTING workspace (one with no source yet) and import it. The workspace keeps its id, env, and secrets. If unfazed.json apps[] omits the supplied/stored current alias, that alias, its current deployment, and its App State are retired."
1956
+ "Link a Git repo from the selected provider to an EXISTING workspace (one with no source yet) and import it. The workspace keeps its id, env, and secrets. If unfazed.json apps[] omits the supplied/stored current alias, that alias, its current deployment, and its App State are retired."
1104
1957
  connectRepo(input: ConnectRepoInput!): ImportRepoPayload!
1105
1958
  syncRepo(input: SyncRepoInput!): ImportRepoPayload!
1959
+ "Give an app with platform git back the files of an earlier commit: one new commit by the caller on its branch, deployed like a push when the app deploys every push. Requires deploy scope."
1960
+ restoreAppVersion(input: RestoreAppVersionInput!): RestoreAppVersionPayload!
1961
+ "Move an app with platform git to a NEW private GitHub repository, created with the caller's own GitHub credential: its whole history (every branch and tag, commits untouched) is pushed there, GitHub's push webhook is installed, and the app deploys from GitHub from then on — nothing redeploys. The platform repository is kept, read-only. Only the app's owner may move it. Idempotent: an app that moved answers with its repository, and a retry finishes a move that failed part-way. Requires deploy scope."
1962
+ moveAppToGitHub(input: MoveAppToGitHubInput!): MoveAppToGitHubPayload!
1963
+ "Give an app with no linked repository its own git repository on the platform now (MCP enable_platform_git; apps get one by themselves at their next change once they serve): its current files become the first commit on main, by the caller, and every later push to main deploys it like a linked repository's. Only the app's owner may turn it on. Refused, with what to fix, when the first push would change what runs: a box, a VM size, schedule or health path set at deploy time instead of in unfazed.json, several aliases without unfazed.json apps[], or files a git import leaves out (.env, .git, node_modules). Idempotent. Requires deploy scope."
1964
+ enablePlatformGit(input: EnablePlatformGitInput!): EnablePlatformGitPayload!
1106
1965
  unlinkRepo(input: UnlinkRepoInput!): DeleteResultPayload!
1107
1966
  installRepoWebhook(input: InstallRepoWebhookInput!): ImportRepoPayload!
1108
1967
  setRepoDeployAlias(input: SetRepoDeployAliasInput!): ImportRepoPayload!
1968
+ "Create an environment (e.g. staging) of a git-linked app: a sibling workspace that deploys another branch of the same repo to its own aliases. Plain env is copied, secrets are not."
1969
+ addRepoEnvironment(input: AddRepoEnvironmentInput!): AddRepoEnvironmentPayload!
1970
+ "Replace the encrypted repository token; empty removes it. Does not deploy. GitHub then uses the account token. Other providers use anonymous access."
1971
+ setRepoToken(workspaceID: ID!, token: String!): ImportRepoPayload!
1109
1972
  setGitHubToken(input: SetGitHubTokenInput!): GitHubTokenStatus!
1110
1973
  deleteGitHubToken: GitHubTokenStatus!
1111
1974
  restartService(input: ServiceControlInput!): ServiceRuntimeStatus!
1112
1975
  stopService(input: ServiceControlInput!): ServiceRuntimeStatus!
1113
- "Permanently delete a deployment and its files snapshot, stopping a running service VM first. Alias-keyed app state is freed once no other deployment uses the alias; the alias's app-scoped env and secrets are reclaimed on that condition plus two more — the alias really was deployed once, and no git source redeploys it — so deleting a failed first build keeps the env staged for the retry. That reclaim is not reversible: secret values are never returned by the API."
1976
+ "Delete ONE app of a workspace and nothing else: every deployment of the alias in that workspace (its VM stopped, its URL and custom domains stop serving), its App State, its app-scoped env and secrets, and its integration bindings. The workspace, its files and defaults, and its other apps keep serving. In a git-linked workspace pushes and syncs skip the alias while unfazed.json apps[] still declares it (Workspace.deletedApps) — remove it from apps[], or bring it back with syncRepo restoreApps. Refused while a git import of the workspace runs. Owner only. The app is kept by the platform for 30 days, so support (the platform operator) can restore it exactly as it was within that time; then it is erased for good. Secret values are never returned."
1977
+ deleteApp(input: DeleteAppInput!): DeleteAppPayload!
1978
+ "Delete a deployment and its files snapshot, stopping a running service VM first. Alias-keyed app state goes with it once no other deployment uses the alias; the alias's app-scoped env and secrets go with it on that condition plus two more — the alias really was deployed once, and no git source redeploys it — so deleting a failed first build keeps the env staged for the retry. What is deleted is kept by the platform for 30 days, so support (the platform operator) can restore it within that time; then it is erased for good. Secret values are never returned by the API."
1114
1979
  deleteDeployment(input: DeleteDeploymentInput!): DeleteResultPayload!
1115
1980
  "Roll an alias back to a PRIOR release: atomically repoint it to the given deployment and bring that snapshot back (always_on restarts warm; on_demand cold-starts on the next request). Also reverts to that release's env/secrets snapshot. Pass the id and opaque aliasRevision of the aliasHeld release you observed; the rollback fails if either changed since deploymentReleases."
1116
1981
  rollbackDeployment(input: RollbackDeploymentInput!): Deployment!
1982
+ "Delete a workspace: its files, env and secrets, and any persistent box disk; with withDeployments, every app it serves too (confirmAliases). Kept by the platform for 30 days, so support (the platform operator) can restore it exactly as it was within that time; then it is erased for good."
1117
1983
  deleteWorkspace(input: DeleteWorkspaceInput!): DeleteResultPayload!
1118
1984
  setAppState(input: SetAppStateInput!): AppStateValue!
1119
1985
  deleteAppState(input: DeleteAppStateInput!): DeleteResultPayload!
1120
1986
  "Rename a linked (claimed) agent. Allowed for the agent's claimer or the operator."
1121
- renameAgent(input: RenameAgentInput!): CurrentActorAgent!
1122
- "Delete a linked (claimed) agent: revoke its token AND delete every workspace and deployment it owns (stopping service VMs). Irreversible."
1123
- deleteAgent(input: DeleteAgentInput!): DeleteResultPayload!
1987
+ renameLinkedAgent(input: RenameLinkedAgentInput!): CurrentActorAgent!
1988
+ "Delete a linked (claimed) agent: revoke its token AND delete everything it owns — its apps and releases (stopping service VMs), its agents and its integration connections. A binding of those connections on another account's app goes too: that app keeps its running release but gets no env from the integration at its next deploy, and the result names it. Allowed for the agent's claimer or the operator, with their own sign-in (not a run, script or job). The agent itself cannot be restored; what its apps held is kept by the platform for 30 days for investigation, then erased."
1989
+ deleteLinkedAgent(input: DeleteLinkedAgentInput!): DeleteLinkedAgentPayload!
1124
1990
  "Issue a fresh access token for a linked (claimed) agent, keeping its identity so it can still redeploy the aliases it owns. Self-registration cannot do this: it always mints a NEW agent, and the platform refuses to let a different owner near an existing alias. Every previous token of the agent stops working immediately, so write the returned value wherever the agent reads it. The token is returned once and is never retrievable again. Allowed for the agent's claimer or the operator."
1125
- rotateAgentToken(input: RotateAgentTokenInput!): AgentTokenPayload!
1991
+ rotateLinkedAgentToken(input: RotateLinkedAgentTokenInput!): LinkedAgentTokenPayload!
1126
1992
  "Issue a one-time code to connect the caller's Telegram to deploy notifications. Returns a t.me deep link to open the bot with the code prefilled; the code expires and is single-use."
1127
1993
  createTelegramLinkCode: TelegramLinkPayload!
1128
1994
  }
@@ -1138,21 +2004,40 @@ type TelegramLinkPayload {
1138
2004
  botUsername: String!
1139
2005
  }
1140
2006
 
1141
- input RenameAgentInput {
2007
+ input RenameLinkedAgentInput {
1142
2008
  agentID: ID!
1143
2009
  name: String!
1144
2010
  }
1145
2011
 
1146
- input DeleteAgentInput {
2012
+ input DeleteLinkedAgentInput {
1147
2013
  agentID: ID!
2014
+ "Exactly the aliases of the agent's liveApps, in any order: every app this delete takes offline. A missing or extra alias refuses the delete and changes nothing."
2015
+ confirmAliases: [String!]
2016
+ }
2017
+
2018
+ "What deleting a linked agent changed outside the agent's own things. A delete that stopped part-way answers with ok false and the error beside it, and still names the apps it detached; deleting again finishes and names them again."
2019
+ type DeleteLinkedAgentPayload {
2020
+ ok: Boolean!
2021
+ "Apps of other accounts (yours, a teammate's) that had one of the agent's connections bound. Each keeps its running release but gets no env from that integration at its next deploy; the provider resource is released once no retained release uses it."
2022
+ affectedApps: [LinkedAgentAffectedApp!]!
2023
+ "Affected apps you cannot see, counted but not named."
2024
+ hiddenAffectedApps: Int!
1148
2025
  }
1149
2026
 
1150
- input RotateAgentTokenInput {
2027
+ "An app that lost a binding of a deleted agent's connection."
2028
+ type LinkedAgentAffectedApp {
2029
+ workspaceID: ID!
2030
+ alias: String!
2031
+ "The integration kind of the binding it lost."
2032
+ kind: String!
2033
+ }
2034
+
2035
+ input RotateLinkedAgentTokenInput {
1151
2036
  agentID: ID!
1152
2037
  }
1153
2038
 
1154
2039
  "A newly issued agent credential. Shown once: only its hash is stored."
1155
- type AgentTokenPayload {
2040
+ type LinkedAgentTokenPayload {
1156
2041
  "The agent this token authenticates as — unchanged by rotation."
1157
2042
  agentID: ID!
1158
2043
  "The bearer token. Copy it now; it cannot be read again."
@@ -1171,10 +2056,16 @@ input RegisterFunctionInput {
1171
2056
  }
1172
2057
 
1173
2058
  input InvokeFunctionInput {
2059
+ "Optional UUID identifying one invocation across retries. A changed function revision or payload conflicts. Servers unable to guarantee replay safety reject this field before execution."
2060
+ invocationID: ID
1174
2061
  functionID: ID!
1175
2062
  payload: JSON
1176
2063
  }
1177
2064
 
2065
+ input DeleteFunctionInput {
2066
+ functionID: ID!
2067
+ }
2068
+
1178
2069
  input PageInput {
1179
2070
  limit: Int
1180
2071
  offset: Int
@@ -1198,12 +2089,16 @@ input WriteWorkspaceTextFileInput {
1198
2089
  workspaceID: ID!
1199
2090
  path: String!
1200
2091
  text: String!
2092
+ "On an app with platform git: the message of the commit this write makes; default names the file. Ignored on other apps."
2093
+ commitMessage: String
1201
2094
  }
1202
2095
 
1203
2096
  input WriteWorkspaceBase64FileInput {
1204
2097
  workspaceID: ID!
1205
2098
  path: String!
1206
2099
  contentBase64: String!
2100
+ "On an app with platform git: the message of the commit this write makes; default names the file. Ignored on other apps."
2101
+ commitMessage: String
1207
2102
  }
1208
2103
 
1209
2104
  input UploadWorkspaceFileInput {
@@ -1214,6 +2109,30 @@ input UploadWorkspaceFileInput {
1214
2109
 
1215
2110
  type WorkspaceFilePayload {
1216
2111
  file: WorkspaceFile!
2112
+ "The commit the write made on an app with platform git; null on any other app, and for an upload."
2113
+ commit: FileCommit
2114
+ }
2115
+
2116
+ "The commit a file write made on an app with platform git."
2117
+ type FileCommit {
2118
+ "The commit; with committed false, the branch head that already held exactly these files. Null while it waits."
2119
+ sha: String
2120
+ branch: String!
2121
+ message: String!
2122
+ "False when git already held exactly these files: no commit was made."
2123
+ committed: Boolean!
2124
+ "True while the commit waits for the git server: the app's files have the write, and its commit lands in order. Should a push change the same files meanwhile, a git.conflict event says so."
2125
+ waiting: Boolean!
2126
+ "The app's commits waiting, while this one does."
2127
+ waitingCount: Int!
2128
+ "Whether the branch head, this write included, is imported and deployed like a push."
2129
+ deploys: Boolean!
2130
+ "Paths a push changed too since the write was made: the head keeps the pushed version (sha is the merge). Empty otherwise."
2131
+ conflicts: [String!]!
2132
+ "The write as it was made, the merge's second parent, when there are conflicts."
2133
+ writeSHA: String
2134
+ "True while platform git is paused (its commit queue is being recovered): the app's files have the write, and they are committed as they are once it is back. Nothing to retry."
2135
+ paused: Boolean!
1217
2136
  }
1218
2137
 
1219
2138
  input ReadWorkspaceFileInput {
@@ -1350,15 +2269,17 @@ input CreateDeploymentInput {
1350
2269
  cronPath: String
1351
2270
  "BOX only: optional VM size. Omit for the platform defaults."
1352
2271
  boxResources: BoxResourcesInput
2272
+ "Server for a NEW app: a server id or name from placementTargets, or control-plane. Omit for the default server. An existing app keeps its server (use a placement move); static sites stay on the control plane."
2273
+ server: String
1353
2274
  }
1354
2275
 
1355
2276
  "A box's requested VM size. Omitted fields use the platform default."
1356
2277
  input BoxResourcesInput {
1357
- "RAM in MiB (default 2048, min 512, max 16384)."
2278
+ "RAM in MiB (default 4096, min 512, max 16384)."
1358
2279
  memMiB: Int
1359
- "vCPU count (default 2, max 8)."
2280
+ "vCPU count (default 4, max 16)."
1360
2281
  vcpus: Int
1361
- "Persistent disk in GiB (default 8, max 64). Applies only when the disk is first created; an existing disk keeps its size."
2282
+ "Persistent disk in GiB (default 32, max 128). Sets the size when the disk is first created; a later redeploy asking for more grows the existing disk before the box boots (it never shrinks, and the effective size may be lower than requested)."
1362
2283
  diskGiB: Int
1363
2284
  }
1364
2285
 
@@ -1380,6 +2301,8 @@ input DeployWorkspaceInput {
1380
2301
  serviceMode: ServiceMode
1381
2302
  "STATIC only; overrides the detected SPA fallback."
1382
2303
  spaMode: Boolean
2304
+ "Server for a NEW app (see CreateDeploymentInput.server); overrides unfazed.json server."
2305
+ server: String
1383
2306
  }
1384
2307
 
1385
2308
  type CreateDeploymentPayload {
@@ -1387,6 +2310,11 @@ type CreateDeploymentPayload {
1387
2310
  }
1388
2311
 
1389
2312
  input ImportRepoInput {
2313
+ "Git provider, default github. Forgejo accepts any public HTTPS instance."
2314
+ provider: String
2315
+ serverURL: String
2316
+ "Optional repository token, encrypted per workspace; never returned."
2317
+ token: String
1390
2318
  repo: String!
1391
2319
  ref: String
1392
2320
  alias: String
@@ -1397,6 +2325,9 @@ input ImportRepoInput {
1397
2325
  }
1398
2326
 
1399
2327
  input ConnectRepoInput {
2328
+ provider: String
2329
+ serverURL: String
2330
+ token: String
1400
2331
  workspaceID: ID!
1401
2332
  repo: String!
1402
2333
  ref: String
@@ -1409,6 +2340,8 @@ input ConnectRepoInput {
1409
2340
  input SyncRepoInput {
1410
2341
  workspaceID: ID!
1411
2342
  force: Boolean
2343
+ "Apps deleted with deleteApp that unfazed.json apps[] still declares (Workspace.deletedApps): their deletion is lifted and this sync, forced, deploys them again. Owner only."
2344
+ restoreApps: [String!]
1412
2345
  }
1413
2346
 
1414
2347
  input UnlinkRepoInput {
@@ -1425,6 +2358,26 @@ input SetRepoDeployAliasInput {
1425
2358
  alias: String!
1426
2359
  }
1427
2360
 
2361
+ input AddRepoEnvironmentInput {
2362
+ "The main app's git-linked workspace."
2363
+ workspaceID: ID!
2364
+ "Environment name such as staging: 1-20 lowercase letters, digits or dashes. Every alias of the environment ends with -<name>."
2365
+ name: String!
2366
+ "The branch the environment deploys on every push."
2367
+ branch: String!
2368
+ "Single-app alias; defaults to <main alias>-<name>."
2369
+ alias: String
2370
+ }
2371
+
2372
+ type AddRepoEnvironmentPayload {
2373
+ workspace: Workspace!
2374
+ gitSource: GitSource
2375
+ "The main app's secret names that were not copied; set them on the environment."
2376
+ skippedSecrets: [String!]!
2377
+ "Why the environment's push webhook could not be installed automatically."
2378
+ webhookError: String
2379
+ }
2380
+
1428
2381
  input SetGitHubTokenInput {
1429
2382
  token: String!
1430
2383
  }
@@ -1446,9 +2399,14 @@ input ListDeploymentsInput {
1446
2399
  }
1447
2400
 
1448
2401
  input CreateRunInput {
1449
- workspaceID: ID!
2402
+ "The workspace whose env, secrets and files the run gets. Optional for an agent's run: the agent's first scoped workspace, else its own."
2403
+ workspaceID: ID
2404
+ "Make this run a task of an agent: it inherits the agent's harness, provider and model, access and approval wherever this input leaves them empty."
2405
+ agentID: ID
1450
2406
  prompt: String
1451
2407
  harness: String
2408
+ "A run preset name: its harness applies when harness is empty, its connection/model override the account's harness setting, its extra env merges below the workspace's own."
2409
+ preset: String
1452
2410
  command: String
1453
2411
  "Shell commands run in /work before the harness; the first failure fails the run."
1454
2412
  setup: [String!]
@@ -1483,6 +2441,7 @@ input FollowUpRunInput {
1483
2441
 
1484
2442
  input ListRunsInput {
1485
2443
  workspaceID: ID
2444
+ agentID: ID
1486
2445
  status: RunStatus
1487
2446
  limit: Int
1488
2447
  offset: Int
@@ -1515,8 +2474,32 @@ input RollbackDeploymentInput {
1515
2474
 
1516
2475
  input DeleteWorkspaceInput {
1517
2476
  workspaceID: ID!
1518
- "Also delete every deployment snapshotted from the workspace (stopping service VMs) instead of refusing while any exist."
2477
+ "Also delete every deployment snapshotted from the workspace (stopping service VMs) instead of refusing while any exist. This takes down EVERY app the workspace serves, so confirmAliases must name exactly those apps (Workspace.liveApps)."
1519
2478
  withDeployments: Boolean
2479
+ "With withDeployments: exactly the aliases of Workspace.liveApps, in any order — every app this delete takes offline. A missing or extra alias refuses the delete and deletes nothing; the error lists the live apps. To delete one app, use deleteApp."
2480
+ confirmAliases: [String!]
2481
+ }
2482
+
2483
+ input DeleteAppInput {
2484
+ workspaceID: ID!
2485
+ "The app to delete: an alias of the workspace."
2486
+ alias: String!
2487
+ }
2488
+
2489
+ "What deleteApp removed."
2490
+ type DeleteAppPayload {
2491
+ ok: Boolean!
2492
+ alias: String!
2493
+ "Deployment records deleted: the live release, retained releases and failed or building attempts."
2494
+ deployments: Int!
2495
+ "Custom domains that served the app and stopped. Their records stay with the alias in this workspace, so a restored app serves them again; removeCustomDomain removes one for good."
2496
+ domains: [String!]!
2497
+ "App-scoped env, secret and REMOVE entries removed."
2498
+ envEntries: Int!
2499
+ "Whether pushes and syncs of this git-linked workspace now skip the alias while unfazed.json apps[] declares it."
2500
+ skippedOnSync: Boolean!
2501
+ "The workspace's apps that are still live, untouched."
2502
+ otherApps: [String!]!
1520
2503
  }
1521
2504
 
1522
2505
  input SetAppStateInput {
@@ -1530,3 +2513,618 @@ input DeleteAppStateInput {
1530
2513
  alias: String!
1531
2514
  key: String!
1532
2515
  }
2516
+
2517
+ "Application feedback shared by the on-page toolbar, dashboard and agents."
2518
+ type ReviewAttachment {
2519
+ id: ID!
2520
+ filename: String!
2521
+ mimeType: String!
2522
+ size: Int!
2523
+ }
2524
+ type ReviewMessage {
2525
+ id: ID!
2526
+ author: String!
2527
+ "Current authenticated profile picture, empty for guests and accounts without one."
2528
+ avatarURL: String!
2529
+ guest: Boolean!
2530
+ body: String!
2531
+ deleted: Boolean!
2532
+ "Whether the current viewer may delete this message."
2533
+ canDelete: Boolean!
2534
+ createdAt: String!
2535
+ attachments: [ReviewAttachment!]!
2536
+ }
2537
+ type ReviewThread {
2538
+ id: ID!
2539
+ "Permanent discussion number within this app generation, including resolved discussions."
2540
+ number: Int!
2541
+ workspaceID: ID!
2542
+ alias: String!
2543
+ deploymentID: ID!
2544
+ page: String!
2545
+ title: String!
2546
+ selector: String!
2547
+ selectedText: String!
2548
+ x: Float!
2549
+ y: Float!
2550
+ viewportWidth: Int!
2551
+ viewportHeight: Int!
2552
+ userAgent: String!
2553
+ resolved: Boolean!
2554
+ createdAt: String!
2555
+ messages: [ReviewMessage!]!
2556
+ }
2557
+ type ReviewPage { threads: [ReviewThread!]! total: Int! canManage: Boolean! }
2558
+ type ReviewLink { id: ID! url: String createdAt: String! expiresAt: String! revoked: Boolean! }
2559
+ type ReviewFile { filename: String! mimeType: String! contentBase64: String! }
2560
+ type ReviewNotificationConnection { id: ID! name: String! }
2561
+ type ReviewNotificationChannel {
2562
+ kind: String!
2563
+ label: String!
2564
+ iconURL: String!
2565
+ connectMethod: String!
2566
+ connections: [ReviewNotificationConnection!]!
2567
+ }
2568
+ type ReviewNotificationSettings {
2569
+ enabled: Boolean!
2570
+ appEnabled: Boolean!
2571
+ connected: Boolean!
2572
+ connectionID: String!
2573
+ channels: [ReviewNotificationChannel!]!
2574
+ }
2575
+ input ReviewAttachmentInput { filename: String! contentBase64: String! }
2576
+ input ReviewCommentInput {
2577
+ deploymentID: ID!
2578
+ threadID: ID
2579
+ body: String!
2580
+ page: String
2581
+ title: String
2582
+ selector: String
2583
+ selectedText: String
2584
+ x: Float
2585
+ y: Float
2586
+ viewportWidth: Int
2587
+ viewportHeight: Int
2588
+ userAgent: String
2589
+ requestID: ID
2590
+ attachments: [ReviewAttachmentInput!]
2591
+ }
2592
+ extend type Query {
2593
+ "Personal feedback notification settings. Omit deploymentID for the account-wide switch."
2594
+ reviewNotificationSettings(deploymentID: ID): ReviewNotificationSettings!
2595
+ reviewThreads(deploymentID: ID!, offset: Int, limit: Int): ReviewPage!
2596
+ reviewThread(deploymentID: ID!, threadID: ID!): ReviewThread!
2597
+ reviewLinks(deploymentID: ID!): [ReviewLink!]!
2598
+ reviewAttachment(deploymentID: ID!, threadID: ID!, attachmentID: ID!): ReviewFile!
2599
+ }
2600
+ extend type Mutation {
2601
+ "Set the account-wide feedback switch, or one app override when deploymentID is present. Defaults are on; delivery is at most once an hour per account."
2602
+ setReviewNotificationSettings(deploymentID: ID, connectionID: ID, enabled: Boolean!): ReviewNotificationSettings!
2603
+ "Create a revocable 30-day guest link, or a one-hour personal toolbar session."
2604
+ createReviewLink(deploymentID: ID!, personal: Boolean = false): ReviewLink!
2605
+ revokeReviewLink(deploymentID: ID!, linkID: ID!): Boolean!
2606
+ addReviewComment(input: ReviewCommentInput!): ReviewThread!
2607
+ "Delete one comment and its attachments. Authors may delete their own; the app owner may moderate all comments."
2608
+ deleteReviewComment(deploymentID: ID!, threadID: ID!, messageID: ID!): Boolean!
2609
+ resolveReviewThread(deploymentID: ID!, threadID: ID!, resolved: Boolean!): ReviewThread!
2610
+ }
2611
+
2612
+ "A commit of an app's platform git repository."
2613
+ type AppCommit {
2614
+ sha: String!
2615
+ "The commit message (its first 64 KiB)."
2616
+ message: String!
2617
+ authorName: String!
2618
+ authorEmail: String!
2619
+ "When the change was authored (RFC3339)."
2620
+ authoredAt: String!
2621
+ "Paths changed against the first parent (every path of a first commit)."
2622
+ filesChanged: Int!
2623
+ "Aliases whose live release runs this commit."
2624
+ deployedTo: [String!]!
2625
+ }
2626
+
2627
+ type AppCommitPage {
2628
+ branch: String!
2629
+ "The branch head; null while the branch has no commits."
2630
+ head: String
2631
+ commits: [AppCommit!]!
2632
+ "Pass as before for the older commits; null on the last page."
2633
+ nextBefore: String
2634
+ "Whether pushes (and restores) deploy the app."
2635
+ autoDeploy: Boolean!
2636
+ "File writes whose commits have not landed yet: the app's files have them, and they land in order as soon as the git server takes them."
2637
+ waitingCommits: Int!
2638
+ "When the oldest waiting write was made (RFC3339); null when none waits."
2639
+ waitingSince: String
2640
+ "The commit every live alias of the app runs, whether or not it is on this page."
2641
+ deployed: [AppDeployedCommit!]!
2642
+ }
2643
+
2644
+ "The commit one live alias runs (AppCommitPage.deployed)."
2645
+ type AppDeployedCommit {
2646
+ alias: String!
2647
+ sha: String!
2648
+ "The release serving the alias."
2649
+ deploymentID: ID!
2650
+ }
2651
+
2652
+ type AppCommitFile {
2653
+ path: String!
2654
+ "added, modified, deleted or type changed."
2655
+ status: String!
2656
+ }
2657
+
2658
+ type AppCommitDiff {
2659
+ commit: AppCommit!
2660
+ files: [AppCommitFile!]!
2661
+ "Unified diff against the first parent, at most 64 KiB (16 KiB per file); lines starting with … say what was left out."
2662
+ patch: String!
2663
+ truncated: Boolean!
2664
+ additions: Int!
2665
+ deletions: Int!
2666
+ }
2667
+
2668
+ input RestoreAppVersionInput {
2669
+ workspaceID: ID!
2670
+ "Full 40-character sha of the commit whose files the app gets back."
2671
+ sha: String!
2672
+ "When set, refuse (changing nothing) unless the branch head is still this commit."
2673
+ expectedHead: String
2674
+ "Message of the new commit; default Restore <short sha> (<its message>)."
2675
+ message: String
2676
+ }
2677
+
2678
+ input MoveAppToGitHubInput {
2679
+ workspaceID: ID!
2680
+ "GitHub account or organization to create the repository in; default the caller's own GitHub account."
2681
+ owner: String
2682
+ "Name of the new repository; default the app's alias."
2683
+ repo: String
2684
+ "When set, refuse (changing nothing) unless the branch head is still this commit."
2685
+ expectedHead: String
2686
+ }
2687
+
2688
+ input EnablePlatformGitInput {
2689
+ workspaceID: ID!
2690
+ }
2691
+
2692
+ type EnablePlatformGitPayload {
2693
+ "The app's source, now deploying from its platform repository."
2694
+ workspace: Workspace!
2695
+ "The repository by alias (https://git.unfz.to/<alias>.git)."
2696
+ cloneURL: String!
2697
+ "The repository by workspace ID, which never changes."
2698
+ stableCloneURL: String!
2699
+ branch: String!
2700
+ "The commit holding the app's files; null for an app without files, whose first push fills it."
2701
+ commitSHA: String
2702
+ "False when the repository already held exactly these files."
2703
+ committed: Boolean!
2704
+ }
2705
+
2706
+ type MoveAppToGitHubPayload {
2707
+ "owner/name of the GitHub repository."
2708
+ repository: String!
2709
+ repoURL: String!
2710
+ "False when an earlier attempt of this move created the repository, or the caller named an empty one."
2711
+ created: Boolean!
2712
+ "The app had moved before: nothing was done."
2713
+ alreadyMoved: Boolean!
2714
+ "The branch head the app deploys, on GitHub too."
2715
+ head: String!
2716
+ "Whether every push to the branch on GitHub deploys the app."
2717
+ webhookInstalled: Boolean!
2718
+ "The platform repository, kept read-only."
2719
+ platformCloneURL: String!
2720
+ "What else happened, such as a push that landed during the move."
2721
+ note: String
2722
+ }
2723
+
2724
+ type RestoreAppVersionPayload {
2725
+ "The new commit; with committed false, the head that already holds that version."
2726
+ sha: String!
2727
+ committed: Boolean!
2728
+ "Whether the new commit is deployed like a push. The deploy is an import queued behind the restore: its deployment is the one whose gitCommit is sha (deploymentsPage with the workspace, then waitForDeployment)."
2729
+ deploys: Boolean!
2730
+ message: String!
2731
+ "The app's branch."
2732
+ branch: String!
2733
+ "The alias a push deploys to, when the link names one; null otherwise (unfazed.json apps[] deploys each of its aliases)."
2734
+ deployAlias: String
2735
+ "The commit restored from (the sha asked for)."
2736
+ restoredFrom: String!
2737
+ "The branch head the new commit was made on; null with committed false."
2738
+ parent: String
2739
+ }
2740
+
2741
+ enum WorkflowRunStatus { PENDING RUNNING COMPLETED FAILED CANCELLED }
2742
+
2743
+ type WorkflowValue { text: String! bytes: Int! truncated: Boolean! error: Boolean! }
2744
+ type WorkflowError { message: String! stack: String code: String truncated: Boolean! }
2745
+ type WorkflowAttribute { key: String! value: String! }
2746
+
2747
+ type WorkflowRunSummary {
2748
+ runID: ID! workflowName: String! displayName: String! module: String!
2749
+ status: WorkflowRunStatus! deploymentID: String!
2750
+ createdAt: String! startedAt: String completedAt: String updatedAt: String! expiredAt: String
2751
+ errorCode: String attributes: [WorkflowAttribute!]!
2752
+ }
2753
+ type WorkflowRunPage { runs: [WorkflowRunSummary!]! nextCursor: String hasMore: Boolean! }
2754
+
2755
+ type WorkflowStep {
2756
+ stepID: ID! stepName: String! displayName: String! module: String!
2757
+ status: String! attempt: Int!
2758
+ createdAt: String! startedAt: String completedAt: String updatedAt: String! retryAfter: String
2759
+ }
2760
+ type WorkflowStepPage { steps: [WorkflowStep!]! nextCursor: String hasMore: Boolean! }
2761
+ type WorkflowWait { correlationID: ID! status: String! createdAt: String! resumeAt: String completedAt: String }
2762
+ type WorkflowHook {
2763
+ hookID: ID! kind: String! status: String! createdAt: String!
2764
+ receivedCount: Int! lastReceivedAt: String disposedAt: String
2765
+ }
2766
+ type WorkflowStreamInfo { name: String! tailIndex: Int done: Boolean }
2767
+ type WorkflowEvent {
2768
+ eventID: ID! eventType: String! correlationID: String createdAt: String!
2769
+ info: JSON! infoTruncated: Boolean!
2770
+ }
2771
+ type WorkflowEventPage { events: [WorkflowEvent!]! nextCursor: String hasMore: Boolean! }
2772
+
2773
+ type WorkflowRun {
2774
+ run: WorkflowRunSummary!
2775
+ input: WorkflowValue output: WorkflowValue error: WorkflowError
2776
+ steps: [WorkflowStep!]! stepsNextCursor: String stepsHasMore: Boolean!
2777
+ waits: [WorkflowWait!]! hooks: [WorkflowHook!]! streams: [WorkflowStreamInfo!]!
2778
+ eventsScanned: Int! eventsComplete: Boolean!
2779
+ waitsTruncated: Boolean! hooksTruncated: Boolean!
2780
+ }
2781
+ type WorkflowStepDetail {
2782
+ step: WorkflowStep! input: WorkflowValue output: WorkflowValue error: WorkflowError
2783
+ events: [WorkflowEvent!]! eventsHasMore: Boolean!
2784
+ }
2785
+ type WorkflowEventDetail { event: WorkflowEvent! data: WorkflowValue }
2786
+ type WorkflowStreamChunk { index: Int! bytes: Int! encoding: String! text: String! truncated: Boolean! }
2787
+ type WorkflowStreamPage {
2788
+ name: String! chunks: [WorkflowStreamChunk!]! nextCursor: String hasMore: Boolean!
2789
+ done: Boolean! tailIndex: Int!
2790
+ }
2791
+ type WorkflowInfo { version: Int! package: String! workflow: String! specVersion: Int workflows: [WorkflowName!]! }
2792
+ type WorkflowName { name: String! displayName: String! module: String! }
2793
+
2794
+ "The app's queue counters, read from its data node (docs/workflow-world.md, Counters)."
2795
+ type WorkflowQueueCounters {
2796
+ "Messages waiting for delivery, due now or later: due plus the delayed ones (ready - due). Delayed messages include wake-ups left by sleeps that were woken early; each is delivered at its time and dropped."
2797
+ ready: Int!
2798
+ "Ready messages whose due time has passed, waiting for a delivery slot."
2799
+ due: Int!
2800
+ "Messages being delivered to the app now."
2801
+ leased: Int!
2802
+ "Messages given up after 24 h of failed deliveries."
2803
+ dead: Int!
2804
+ "The earliest due time of a due message; null when none is due (delayed messages do not count)."
2805
+ oldestDueAt: String
2806
+ "Bytes stored and charged: live messages, idempotency keys and dead messages."
2807
+ bytes: Int!
2808
+ "Dead messages dropped over the dead-letter cap, oldest first."
2809
+ deadEvicted: Int!
2810
+ }
2811
+ type WorkflowDeadMessage {
2812
+ messageID: ID! queueName: String! contentType: String! createdAt: String! deadAt: String!
2813
+ attempt: Int! deliveryCount: Int! size: Int! payloadPreview: String
2814
+ }
2815
+ type WorkflowQueue { counters: WorkflowQueueCounters! dead: [WorkflowDeadMessage!]! deadNextCursor: String deadUnavailable: Boolean! }
2816
+
2817
+ extend type Query {
2818
+ workflowInfo(alias: String!): WorkflowInfo!
2819
+ workflowRuns(alias: String!, status: [WorkflowRunStatus!], workflowName: String, limit: Int, cursor: String): WorkflowRunPage!
2820
+ workflowRun(alias: String!, runID: ID!): WorkflowRun!
2821
+ workflowSteps(alias: String!, runID: ID!, limit: Int, cursor: String): WorkflowStepPage!
2822
+ workflowStep(alias: String!, runID: ID!, stepID: ID!): WorkflowStepDetail!
2823
+ workflowEvents(alias: String!, runID: ID!, correlationID: String, newestFirst: Boolean, limit: Int, cursor: String): WorkflowEventPage!
2824
+ workflowEvent(alias: String!, runID: ID!, eventID: ID!): WorkflowEventDetail!
2825
+ workflowStream(alias: String!, runID: ID!, name: String!, limit: Int, cursor: String): WorkflowStreamPage!
2826
+ workflowQueue(alias: String!, deadLimit: Int, deadCursor: String): WorkflowQueue!
2827
+ }
2828
+
2829
+ input WakeWorkflowRunInput { alias: String! runID: ID! correlationIDs: [ID!] }
2830
+ input CancelWorkflowRunInput { alias: String! runID: ID! reason: String }
2831
+ input ReplayDeadWorkflowMessagesInput { alias: String! messageIDs: [ID!] all: Boolean }
2832
+ type WakeWorkflowRunPayload { stoppedCount: Int! }
2833
+ type CancelWorkflowRunPayload { runID: ID! status: WorkflowRunStatus! }
2834
+ type ReplayDeadWorkflowMessagesPayload { replayed: Int! skipped: Int! remainingDead: Int! }
2835
+
2836
+ extend type Mutation {
2837
+ wakeWorkflowRun(input: WakeWorkflowRunInput!): WakeWorkflowRunPayload!
2838
+ cancelWorkflowRun(input: CancelWorkflowRunInput!): CancelWorkflowRunPayload!
2839
+ replayDeadWorkflowMessages(input: ReplayDeadWorkflowMessagesInput!): ReplayDeadWorkflowMessagesPayload!
2840
+ }
2841
+
2842
+ "One piece a deletion holds: a release, the workspace's files and env, an app's env, its App State, a box disk, an integration resource."
2843
+ type DeletedPiece {
2844
+ id: ID!
2845
+ kind: String!
2846
+ "Where it is held: control-plane, app-state:<namespace>, box-disk:<workspace>, provider:<kind>."
2847
+ host: String!
2848
+ location: String!
2849
+ bytes: Float!
2850
+ "Why it is no longer held (expired, erasure, budget, restored); null while it is."
2851
+ closed: String
2852
+ closedAt: String
2853
+ }
2854
+
2855
+ "One deletion the platform holds (docs/soft-delete.md): what was deleted, whose, when and by whom, and until when it can be restored. Platform operator only."
2856
+ type DeletedItem {
2857
+ deletionID: ID!
2858
+ "app, workspace, release, run_agent or linked_agent."
2859
+ subjectKind: String!
2860
+ subjectID: String!
2861
+ what: String!
2862
+ workspaceID: ID
2863
+ workspaceName: String
2864
+ aliases: [String!]!
2865
+ "Custom domains a restore serves again."
2866
+ domains: [String!]!
2867
+ ownerID: String
2868
+ deletedAt: String!
2869
+ deletedBy: String
2870
+ "The surface the delete came through: mcp, graphql, form, api, system."
2871
+ via: String!
2872
+ "delete_app, delete_workspace, delete_deployment, git_retired, delete_agent, delete_linked_agent."
2873
+ path: String!
2874
+ "When everything it holds is erased for good."
2875
+ purgeAfter: String!
2876
+ "held, restoring, restored or purged."
2877
+ state: String!
2878
+ restoredAt: String
2879
+ restoredBy: String
2880
+ "Bytes it holds on the control plane."
2881
+ bytes: Float!
2882
+ pieces: [DeletedPiece!]!
2883
+ "Whether restoreDeleted can bring it back now; refusal says why not."
2884
+ restorable: Boolean!
2885
+ refusal: String
2886
+ "Other open deletions of the same subject (a delete finished by deleting again later) that restoring this one brings back with it."
2887
+ restoresWith: [ID!]!
2888
+ "Other open deletions of the same app (a release deleted on its own, or the app deleted after one of its releases); each is restored on its own."
2889
+ related: [ID!]!
2890
+ }
2891
+
2892
+ "What restoreDeleted brought back."
2893
+ type DeletedRestorePayload {
2894
+ deletionID: ID!
2895
+ "restored, or already_restored for one restored before (its apps were started again)."
2896
+ status: String!
2897
+ workspaceID: ID
2898
+ releases: Int!
2899
+ apps: [String!]!
2900
+ domains: [String!]!
2901
+ bindings: [String!]!
2902
+ "App env entries set again since the delete, which kept their current values."
2903
+ envKept: [String!]!
2904
+ "Where each restored app serves, or why it did not start yet."
2905
+ placement: [String!]!
2906
+ "Other deletions of the same subject restored with it."
2907
+ with: [ID!]!
2908
+ }
2909
+
2910
+ "What purgeDeleted erased."
2911
+ type DeletedPurgePayload {
2912
+ deletionID: ID!
2913
+ purged: Int!
2914
+ bytes: Float!
2915
+ }
2916
+
2917
+ extend type Query {
2918
+ "The deleted apps, workspaces and releases the platform holds, newest first, a page at a time, with what a restore would bring back and until when. Platform operator only; anyone else is refused."
2919
+ deletedItems(
2920
+ "Only deletions in this state: held, restoring, restored or purged."
2921
+ state: String
2922
+ "Matching deletions to skip, for the next page."
2923
+ offset: Int
2924
+ "At most this many, newest first (default 50, at most 500)."
2925
+ limit: Int
2926
+ ): [DeletedItem!]!
2927
+ }
2928
+
2929
+ extend type Mutation {
2930
+ "Restore one deletion with everything it held, under the same ids: the workspace, releases with their env and secrets, integration bindings, and the alias on its old epoch, so App State, the box disk and custom domains reattach and the app serves again as before. Refused, changing nothing, while its alias is held by another app (named), once anything of it was purged, or when a piece is missing. Safe to retry. Platform operator only."
2931
+ restoreDeleted(deletionID: ID!): DeletedRestorePayload!
2932
+ "Erase one deletion now instead of after its retention, overriding an investigation hold: files, records, App State, the box disk, integration resources at their providers, custom domains. It can never be restored afterwards. reason is kept with the erasure. Platform operator only."
2933
+ purgeDeleted(deletionID: ID!, reason: String!): DeletedPurgePayload!
2934
+ }
2935
+
2936
+ "One file of writeWorkspaceFiles, as MCP write_workspace_files takes it."
2937
+ input WorkspaceFileWriteInput {
2938
+ "Relative path, such as server.ts or assets/logo.png."
2939
+ path: String!
2940
+ "The file: UTF-8 text, or base64-encoded bytes with encoding base64."
2941
+ content: String!
2942
+ "text (the default) or base64."
2943
+ encoding: String
2944
+ }
2945
+
2946
+ input WriteWorkspaceFilesInput {
2947
+ workspaceID: ID!
2948
+ "Written in the given order; a repeated path keeps its last content. At most 100 files, 16 MiB each and 32 MiB in all (decoded) per call: more fails the whole call before anything is written."
2949
+ files: [WorkspaceFileWriteInput!]!
2950
+ "On an app with platform git: the message of the call's ONE commit; default names the files. Ignored on other apps."
2951
+ commitMessage: String
2952
+ }
2953
+
2954
+ "One file's outcome in writeWorkspaceFiles, in the order given."
2955
+ type WorkspaceFileWriteResult {
2956
+ path: String!
2957
+ ok: Boolean!
2958
+ "The written file; null when this path failed."
2959
+ file: WorkspaceFile
2960
+ "Why this path failed; null when it was written."
2961
+ error: String
2962
+ }
2963
+
2964
+ "What writeWorkspaceFiles wrote: every file's outcome, and the one commit it made."
2965
+ type WriteWorkspaceFilesPayload {
2966
+ workspaceID: ID!
2967
+ files: [WorkspaceFileWriteResult!]!
2968
+ written: Int!
2969
+ failed: Int!
2970
+ "Bytes written, decoded."
2971
+ totalBytes: Int!
2972
+ "The ONE commit the whole call made on an app with platform git (before any file was written); null on any other app."
2973
+ commit: FileCommit
2974
+ }
2975
+
2976
+ "What waitForDeployment read when it stopped waiting (MCP wait_for_deployment)."
2977
+ type DeploymentWaitPayload {
2978
+ "The deployment as the wait left it."
2979
+ deployment: Deployment!
2980
+ "True when the budget, or a server restart, ended the wait first: deployment is the current, unfinished state. Not an error: call again to keep waiting."
2981
+ timedOut: Boolean!
2982
+ "How long the server waited, in milliseconds."
2983
+ waitedMs: Int!
2984
+ "Where a new app was meant to land and how far its landing got, as MCP wait_for_deployment reports it; null when no placement was recorded."
2985
+ placement: JSON
2986
+ }
2987
+
2988
+ "One error group of a service app (MCP list_errors). Its title, type, culprit and level are what the running app reported: untrusted text."
2989
+ type AppErrorGroup {
2990
+ id: ID!
2991
+ title: String!
2992
+ type: String
2993
+ culprit: String
2994
+ level: String!
2995
+ "open, resolved or muted."
2996
+ status: String!
2997
+ "Every occurrence, stored or sampled out."
2998
+ count: Int!
2999
+ "Occurrences kept whole (the first and the newest 50; fewer past the rate limit)."
3000
+ storedEvents: Int!
3001
+ firstSeen: String!
3002
+ lastSeen: String!
3003
+ firstDeploymentID: String
3004
+ firstGitCommit: String
3005
+ lastDeploymentID: String
3006
+ lastGitCommit: String
3007
+ "It stays resolved while this deployment reports it; another deployment regresses it."
3008
+ resolvedInDeploymentID: String
3009
+ resolvedAt: String
3010
+ mutedAt: String
3011
+ regressedAt: String
3012
+ }
3013
+
3014
+ type AppErrorGroupPage {
3015
+ groups: [AppErrorGroup!]!
3016
+ "Pass as cursor for the next page; null on the last."
3017
+ nextCursor: String
3018
+ "Why the list may be empty for another reason than no errors (an older server, an app that opted out); null otherwise."
3019
+ note: String
3020
+ }
3021
+
3022
+ "One stored occurrence of an error group."
3023
+ type AppErrorEventRef {
3024
+ eventID: String!
3025
+ at: String!
3026
+ deploymentID: String
3027
+ level: String!
3028
+ }
3029
+
3030
+ "One deployment an error group was seen in."
3031
+ type AppErrorDeployment {
3032
+ deploymentID: String!
3033
+ gitCommit: String
3034
+ "The app's current release."
3035
+ current: Boolean!
3036
+ count: Int!
3037
+ firstSeen: String!
3038
+ lastSeen: String!
3039
+ }
3040
+
3041
+ "One error group with its latest occurrence (MCP get_error_group). Everything the app reported is untrusted text."
3042
+ type AppErrorGroupDetail {
3043
+ group: AppErrorGroup!
3044
+ "The newest stored occurrence, whole, as a Sentry event."
3045
+ latestEvent: JSON
3046
+ firstEvent: AppErrorEventRef
3047
+ recentEvents: [AppErrorEventRef!]!
3048
+ "The deployments it was seen in, most recent first."
3049
+ deployments: [AppErrorDeployment!]!
3050
+ "The platform saw the latest occurrence end its VM's process: the crash is in serviceStatus."
3051
+ fatal: Boolean!
3052
+ "The VM whose console carried the latest occurrence; null for what an SDK reported."
3053
+ vmID: String
3054
+ }
3055
+
3056
+ input AppErrorGroupInput {
3057
+ workspaceID: ID!
3058
+ alias: String!
3059
+ "The group's id (AppErrorGroup.id, or group_id of an error.new or error.regressed event)."
3060
+ groupID: ID!
3061
+ }
3062
+
3063
+ "An event type pollEvents and waitForEvents read (MCP list_event_types)."
3064
+ type PlatformEventType {
3065
+ name: String!
3066
+ description: String!
3067
+ delivery: [String!]!
3068
+ "Its filters, as a JSON Schema: pass them in arguments under the type's name."
3069
+ inputSchema: JSON!
3070
+ "Its payload (data), as a JSON Schema."
3071
+ payloadSchema: JSON!
3072
+ }
3073
+
3074
+ "Which events to read (MCP poll_events)."
3075
+ input PlatformEventsInput {
3076
+ "Event types; omit for every type you may receive."
3077
+ names: [String!]
3078
+ "Filters per type, keyed by its name, e.g. {\"deployment.finished\": {\"alias\": \"shop\", \"status\": \"FAILED\"}}."
3079
+ arguments: JSON
3080
+ "Where to continue; omit to start from now (nothing is replayed)."
3081
+ cursor: String
3082
+ "At most this many events."
3083
+ max: Int
3084
+ }
3085
+
3086
+ "One event (deployment.finished, service.crashed, error.new, git.conflict, …). Its data is minimal: ids, statuses, excerpts."
3087
+ type PlatformEvent {
3088
+ eventID: ID!
3089
+ name: String!
3090
+ timestamp: String!
3091
+ data: JSON!
3092
+ }
3093
+
3094
+ type PlatformEventPage {
3095
+ events: [PlatformEvent!]!
3096
+ "Pass back as cursor: at once while hasMore, otherwise after nextPollMs (or to waitForEvents)."
3097
+ cursor: String!
3098
+ "Events may be missing (a cursor older than the history, or a control-plane crash): re-read state."
3099
+ truncated: Boolean!
3100
+ hasMore: Boolean!
3101
+ nextPollMs: Int!
3102
+ "waitForEvents only: the budget ended the wait with no event. Call again with cursor."
3103
+ timedOut: Boolean!
3104
+ }
3105
+
3106
+ extend type Query {
3107
+ "Block until a deployment leaves BUILDING (and a new app's direct landing ends) or timeoutSeconds pass (default 45, at most 50), then return it — the server-side wait of MCP wait_for_deployment, woken by the deploy pipeline itself, so a client never polls. A timeout is not an error: timedOut is true and the deployment is the current state; call again to keep waiting."
3108
+ waitForDeployment(id: ID!, timeoutSeconds: Int): DeploymentWaitPayload!
3109
+ "A service app's error groups, most recently seen first (MCP list_errors). status: open, resolved or muted (default all); limit 1 to 100 (default 20); cursor is the previous page's nextCursor."
3110
+ appErrors(workspaceID: ID!, alias: String!, status: String, limit: Int, cursor: String): AppErrorGroupPage!
3111
+ "One error group of a service app with its latest occurrence, recent occurrences and the deployments it was seen in (MCP get_error_group)."
3112
+ appErrorGroup(workspaceID: ID!, alias: String!, groupID: ID!): AppErrorGroupDetail!
3113
+ "The event types pollEvents and waitForEvents read, with each type's filters and payload (MCP list_event_types)."
3114
+ platformEventTypes: [PlatformEventType!]!
3115
+ "What happened to the apps, runs and custom domains you can see since cursor (MCP poll_events; the cursors are the same). Omit cursor to start from now."
3116
+ pollEvents(input: PlatformEventsInput): PlatformEventPage!
3117
+ "pollEvents that blocks server-side until an event arrives or timeoutSeconds pass (default 45, at most 50; MCP wait_for_events). A timeout is not an error: timedOut is true, with a cursor to call again with."
3118
+ waitForEvents(input: PlatformEventsInput, timeoutSeconds: Int): PlatformEventPage!
3119
+ }
3120
+
3121
+ extend type Mutation {
3122
+ "Create or replace MANY files in ONE call (MCP write_workspace_files): each file text or base64. On an app with platform git the whole call is ONE commit on its branch (commit), made before any file is written: if it fails, the call fails and nothing is written. Otherwise not transactional: files are written in order, each atomically, and a file's failure (files[].error) does not undo the others. Requires deploy scope."
3123
+ writeWorkspaceFiles(input: WriteWorkspaceFilesInput!): WriteWorkspaceFilesPayload!
3124
+ "Mark an error group resolved in the app's current deployment, once its fix is deployed (MCP resolve_error_group). Requires deploy scope."
3125
+ resolveAppErrorGroup(input: AppErrorGroupInput!): AppErrorGroup!
3126
+ "Mute an expected error group: still counted, never regresses or sends events (MCP mute_error_group). Requires deploy scope."
3127
+ muteAppErrorGroup(input: AppErrorGroupInput!): AppErrorGroup!
3128
+ "Open a resolved or muted error group again (MCP reopen_error_group). Requires deploy scope."
3129
+ reopenAppErrorGroup(input: AppErrorGroupInput!): AppErrorGroup!
3130
+ }