unfazed 0.6.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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!
@@ -367,6 +598,8 @@ type Deployment {
367
598
  gitRepo: String
368
599
  gitCommit: String
369
600
  createdAt: String!
601
+ "The deploy's measured timeline; null before its build finished, and for deployments from before it was recorded."
602
+ timings: DeploymentTimings
370
603
  """
371
604
  The runtime target serving this exact current deployment: an enrolled worker
372
605
  or the control plane. Null for historical releases and residual legacy
@@ -375,6 +608,20 @@ type Deployment {
375
608
  worker: DeploymentWorker
376
609
  }
377
610
 
611
+ "A release summary. History cannot recursively expand the same history."
612
+ type DeploymentRelease {
613
+ id: ID!
614
+ aliasHeld: Boolean!
615
+ aliasRevision: String!
616
+ status: DeploymentStatus!
617
+ error: String
618
+ serviceMode: ServiceMode!
619
+ previewUrl: String!
620
+ gitCommit: String
621
+ size: String!
622
+ createdAt: String!
623
+ }
624
+
378
625
  "The authoritative runtime target assigned to a current deployment."
379
626
  type DeploymentWorker {
380
627
  id: String!
@@ -393,9 +640,31 @@ type DeploymentEvent {
393
640
  message: String!
394
641
  vmID: String
395
642
  metadata: JSON
643
+ "The phases a timing event (deployment.build_finished, deployment.phases) measured, as numbers; null for any other event."
644
+ timings: DeploymentTimings
396
645
  createdAt: String!
397
646
  }
398
647
 
648
+ """
649
+ A deploy's measured timeline in milliseconds: what its
650
+ deployment.build_finished and deployment.phases events record (the latest of
651
+ each, the READY summary over the build's), as the SDK's
652
+ parseDeploymentTimings reads them.
653
+ """
654
+ type DeploymentTimings {
655
+ "From the deployment's start until it was READY; null before READY."
656
+ totalMs: Int
657
+ "The build's wall time."
658
+ buildMs: Int
659
+ "The promotion's wall time."
660
+ promoteMs: Int
661
+ "The other measured phases, name to milliseconds: the build VM's (vm_install, vm_build, ...) and the promotion's own (admission, ...)."
662
+ phases: JSON!
663
+ buildMode: String
664
+ artifactTransport: String
665
+ buildMemoryMiB: Int
666
+ }
667
+
399
668
  type ServiceRuntimeStatus {
400
669
  deploymentID: ID!
401
670
  alias: String
@@ -412,6 +681,66 @@ type ServiceRuntimeStatus {
412
681
  lastStoppedAt: String
413
682
  restartCount: Int!
414
683
  lastError: String
684
+ """
685
+ The app's recent crashes on its server: its VM stopped without being asked
686
+ to. Null when it has had none since that server's runtime last started.
687
+ """
688
+ crashes: ServiceCrashes
689
+ "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."
690
+ crashesTracked: Boolean!
691
+ "The deployment's measured deploy timeline (MCP get_service_status timings)."
692
+ timings: DeploymentTimings
693
+ "Set when the alias no longer runs this release (or never did); health is then superseded (failed for an attempt that never went live) unless this release still serves by its own preview URL."
694
+ notLive: ReleaseNotLive
695
+ }
696
+
697
+ """
698
+ A release its alias does not run: a deploy or rollback replaced it, the
699
+ release that did was deleted, or it failed before it went live. Nothing runs
700
+ it for its app, so retrying a read never changes that; a rollback makes it
701
+ live again.
702
+ """
703
+ type ReleaseNotLive {
704
+ alias: String!
705
+ "The release the alias runs now; null when it runs none, or one the caller may not see."
706
+ liveDeploymentID: ID
707
+ "When that release took the alias."
708
+ liveSince: String
709
+ "When this release stopped serving the alias; null for an attempt that never went live."
710
+ replacedAt: String
711
+ "True for an attempt that failed before it went live."
712
+ neverLive: Boolean!
713
+ "All of the above in one sentence."
714
+ message: String!
715
+ }
716
+
717
+ "Recent crashes of a service on the server that runs it."
718
+ type ServiceCrashes {
719
+ "At least 3 crashes within the last hour."
720
+ looping: Boolean!
721
+ lastHour: Int!
722
+ last24h: Int!
723
+ "Crashes since the server's runtime last started."
724
+ total: Int!
725
+ "Newest first, at most 5; only the newest carries logTail."
726
+ recent: [ServiceCrash!]!
727
+ }
728
+
729
+ "One unexpected stop of a service's VM."
730
+ type ServiceCrash {
731
+ at: String!
732
+ vmID: String
733
+ "running (the VM was serving) or starting (the app failed before it was ready)."
734
+ phase: String!
735
+ "app_exit, app_signal, out_of_memory, kernel_panic, guest_stopped, unknown, health_check, host_killed or vm_error."
736
+ cause: String!
737
+ "The app process's exit status (128+signal when a signal ended it), when its VM reported it."
738
+ exitCode: Int
739
+ signal: String
740
+ "One line saying what happened."
741
+ message: String!
742
+ "The last lines of the VM's console before it stopped (the app's output, secrets redacted)."
743
+ logTail: String
415
744
  }
416
745
 
417
746
  type ServiceRuntimeLogs {
@@ -422,6 +751,8 @@ type ServiceRuntimeLogs {
422
751
  stdoutBytes: Int!
423
752
  stderrBytes: Int!
424
753
  tailBytes: Int!
754
+ "Set when the alias no longer runs this release (or never did): the logs are whatever is still held for it, often none."
755
+ notLive: ReleaseNotLive
425
756
  }
426
757
 
427
758
  type AppStateValue {
@@ -445,6 +776,8 @@ type IntegrationKind {
445
776
  kind: String!
446
777
  label: String!
447
778
  iconURL: String
779
+ "Lowercase catalog grouping labels: ai, database, observability, …"
780
+ tags: [String!]!
448
781
  # who supplies the backend: "platform" (unfazed runs it) or "user" (BYO)
449
782
  provenance: String!
450
783
  # how the owner connects: "none" (no connection), "fields" (a form), "link" (an auth flow)
@@ -458,7 +791,55 @@ type IntegrationKind {
458
791
  "Safe isolation mode selected when bindIntegration omits mode; null for providers without app runtime."
459
792
  defaultMode: String
460
793
  runtime: IntegrationRuntime!
794
+ "Tools and skills this provider contributes to a bound app's bot tasks. No credentials or server endpoints."
795
+ agent: IntegrationAgent!
461
796
  surfaces: [IntegrationSurface!]!
797
+ "True when the provider can read what one scoped binding's per-app resource consumed (integrationBindingUsage)."
798
+ reportsUsage: Boolean!
799
+ "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."
800
+ bindOptions: [IntegrationBindOption!]!
801
+ }
802
+ "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."
803
+ type IntegrationBindOption {
804
+ name: String!
805
+ label: String!
806
+ description: String!
807
+ "Which apps the option exists for: app (every app) or environment (only a git environment of another app). Elsewhere bindIntegration refuses it."
808
+ appliesTo: String!
809
+ "The value recorded when bindIntegration omits the option."
810
+ default: String!
811
+ choices: [IntegrationBindChoice!]!
812
+ }
813
+ type IntegrationBindChoice {
814
+ value: String!
815
+ label: String!
816
+ description: String!
817
+ "What the owner must know before choosing this value (e.g. that it copies production data); null when nothing."
818
+ warning: String
819
+ }
820
+ "One bind option value of a binding."
821
+ type IntegrationBindOptionValue {
822
+ name: String!
823
+ value: String!
824
+ "True when the binding predates the option: nothing was chosen or recorded, and value is what such bindings were created with."
825
+ legacy: Boolean!
826
+ }
827
+ input IntegrationBindOptionInput {
828
+ name: String!
829
+ value: String!
830
+ }
831
+ type IntegrationAgent {
832
+ revision: String!
833
+ servers: [IntegrationAgentServer!]!
834
+ skills: [String!]!
835
+ }
836
+ type IntegrationAgentServer {
837
+ name: String!
838
+ description: String!
839
+ transport: String!
840
+ auth: String!
841
+ readTools: [String!]!
842
+ writeTools: [String!]!
462
843
  }
463
844
  type IntegrationSurface {
464
845
  id: String!
@@ -501,6 +882,18 @@ type IntegrationConnection {
501
882
  grantedProjectIDs: [ID!]!
502
883
  createdAt: String!
503
884
  updatedAt: String!
885
+ "OAuth state of the kind's MCP server (null for kinds without one): runs get the server only while it is active."
886
+ authorization: IntegrationConnectionAuthorization
887
+ }
888
+ "The owner's OAuth grant for a connection's MCP server. The platform is the OAuth client; tokens stay on the control plane."
889
+ type IntegrationConnectionAuthorization {
890
+ "required (no usable grant), active, or failed (the server refused a refresh; authorize again)"
891
+ status: String!
892
+ "The protected resource: the MCP endpoint the grant is for."
893
+ resource: String!
894
+ scopes: [String!]!
895
+ authorizedAt: String
896
+ error: String
504
897
  }
505
898
  type IntegrationFieldValue {
506
899
  name: String!
@@ -524,17 +917,59 @@ type IntegrationBinding {
524
917
  status: String!
525
918
  "Set when the binding rides a GRANTED connection: the lender's identity. The lender can always see and sever such bindings."
526
919
  connectionOwner: String
920
+ "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."
921
+ reportsUsage: Boolean!
922
+ "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."
923
+ options: [IntegrationBindOptionValue!]!
924
+ "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."
925
+ warnings: [String!]!
926
+ "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)."
927
+ revealAvailable: Boolean!
928
+ }
929
+ "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."
930
+ type IntegrationUsage {
931
+ bindingID: ID!
932
+ kind: String!
933
+ "Start of the window the figures cover (RFC3339); null means since the resource was created."
934
+ periodStart: String
935
+ "End of the window (RFC3339); null means now."
936
+ periodEnd: String
937
+ metrics: [IntegrationUsageMetric!]!
938
+ "Optional https link to this app's resource in the provider's console."
939
+ consoleURL: String
940
+ "When unfazed read these figures from the provider (RFC3339)."
941
+ fetchedAt: String!
942
+ "Why the figures are unavailable (the provider failed, timed out or reported malformed data); null on success. Never carries credential values."
943
+ error: String
944
+ }
945
+ type IntegrationUsageMetric {
946
+ "Stable machine name, e.g. compute_hours."
947
+ name: String!
948
+ label: String!
949
+ value: Float!
950
+ "Display unit; may be empty for plain counts."
951
+ unit: String!
952
+ "Optional cap configured for this app, in the same unit."
953
+ limit: Float
527
954
  }
528
955
  type IntegrationConnectionPayload {
529
956
  connection: IntegrationConnection!
530
957
  }
531
958
  type IntegrationBindingPayload {
532
959
  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."
960
+ "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
961
  revealCommand: String
535
962
  "RFC3339 time the unused reveal stops working."
536
963
  revealExpiresAt: String
537
964
  }
965
+ "The single one-time .env reveal of one binding, minted by revealIntegrationEnv."
966
+ type IntegrationEnvReveal {
967
+ binding: IntegrationBinding!
968
+ "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."
969
+ revealCommand: String!
970
+ "RFC3339 time the unused reveal stops working."
971
+ revealExpiresAt: String!
972
+ }
538
973
  "Account-level non-secret event routing. Credential/runtime grants never use this type."
539
974
  type IntegrationSubscription {
540
975
  id: ID!
@@ -609,6 +1044,142 @@ input BindIntegrationInput {
609
1044
  alias: String!
610
1045
  "Optional provider-supported isolation mode. Omit to use the provider's safe default (scoped for Postgres)."
611
1046
  mode: String
1047
+ "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."
1048
+ options: [IntegrationBindOptionInput!]
1049
+ }
1050
+ "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."
1051
+ type RunHarnessSetting {
1052
+ harness: String!
1053
+ connectionID: ID!
1054
+ connectionName: String
1055
+ kind: String
1056
+ model: String
1057
+ updatedAt: String!
1058
+ }
1059
+ type RunHarnessSettingPayload {
1060
+ setting: RunHarnessSetting!
1061
+ }
1062
+ "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."
1063
+ type RunPreset {
1064
+ name: String!
1065
+ description: String!
1066
+ harness: String!
1067
+ connectionID: ID
1068
+ "Set when the referenced connection still exists: its display name and kind."
1069
+ connectionName: String
1070
+ kind: String
1071
+ model: String
1072
+ env: [RunPresetEnv!]!
1073
+ createdAt: String!
1074
+ updatedAt: String!
1075
+ }
1076
+ type RunPresetEnv {
1077
+ name: String!
1078
+ value: String!
1079
+ }
1080
+ type RunPresetPayload {
1081
+ preset: RunPreset!
1082
+ }
1083
+ input RunPresetEnvInput {
1084
+ name: String!
1085
+ value: String!
1086
+ }
1087
+ input SaveRunPresetInput {
1088
+ name: String!
1089
+ description: String
1090
+ harness: String!
1091
+ connectionID: ID
1092
+ model: String
1093
+ env: [RunPresetEnvInput!]
1094
+ }
1095
+ input DeleteRunPresetInput {
1096
+ name: String!
1097
+ }
1098
+ "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."
1099
+ type RunTrigger {
1100
+ id: ID!
1101
+ name: String!
1102
+ enabled: Boolean!
1103
+ "\"webhook\" (generic HMAC/bearer) or \"github\" (X-Hub-Signature-256)."
1104
+ source: String!
1105
+ "Event names that fire (github: X-GitHub-Event); empty fires on every event."
1106
+ filter: [String!]!
1107
+ template: RunTriggerTemplate!
1108
+ cooldownSeconds: Int!
1109
+ "Allow parallel firings; off = one at a time, later firings queue (owner decision D3)."
1110
+ allowParallel: Boolean!
1111
+ "The POST endpoint to configure at the sender: /hooks/runs/{id}."
1112
+ hookPath: String!
1113
+ "Recent firings, newest first."
1114
+ firings: [RunTriggerFiring!]!
1115
+ "Firings waiting for a slot (cooldown or a still-running previous firing)."
1116
+ queued: Int!
1117
+ createdAt: String!
1118
+ updatedAt: String!
1119
+ }
1120
+ type RunTriggerTemplate {
1121
+ workspaceID: ID!
1122
+ alias: String
1123
+ harness: String
1124
+ preset: String
1125
+ "May hold {{event.<dotted.path>}} placeholders, interpolated from the JSON payload."
1126
+ prompt: String!
1127
+ access: String
1128
+ maxSeconds: Int
1129
+ metadata: JSON
1130
+ }
1131
+ type RunTriggerFiring {
1132
+ id: ID!
1133
+ at: String!
1134
+ "started | queued | skipped | failed | expired"
1135
+ status: String!
1136
+ summary: String
1137
+ runID: ID
1138
+ reason: String
1139
+ }
1140
+ type RunTriggerPayload {
1141
+ trigger: RunTrigger!
1142
+ }
1143
+ input RunTriggerTemplateInput {
1144
+ workspaceID: ID!
1145
+ alias: String
1146
+ harness: String
1147
+ preset: String
1148
+ prompt: String!
1149
+ access: String
1150
+ maxSeconds: Int
1151
+ metadata: JSON
1152
+ }
1153
+ input SaveRunTriggerInput {
1154
+ "Empty creates; a set id updates that trigger."
1155
+ id: ID
1156
+ name: String!
1157
+ enabled: Boolean!
1158
+ source: String!
1159
+ "Empty keeps the existing secret (a fresh one is generated on create); set replaces it."
1160
+ secret: String
1161
+ filter: [String!]
1162
+ template: RunTriggerTemplateInput!
1163
+ cooldownSeconds: Int
1164
+ allowParallel: Boolean
1165
+ }
1166
+ input RunTriggerIDInput {
1167
+ id: ID!
1168
+ }
1169
+ input SetRunHarnessProviderInput {
1170
+ harness: String!
1171
+ connectionID: ID!
1172
+ model: String
1173
+ }
1174
+ input ClearRunHarnessProviderInput {
1175
+ harness: String!
1176
+ }
1177
+ "One of the caller's own connections eligible to back a harness: its kind implements harness credentials and actually serves this one."
1178
+ type RunHarnessProviderChoice {
1179
+ harness: String!
1180
+ connectionID: ID!
1181
+ name: String!
1182
+ kind: String!
612
1183
  }
613
1184
  input IntegrationIDInput {
614
1185
  id: ID!
@@ -681,6 +1252,8 @@ type DeploymentEventPage {
681
1252
  type Invocation {
682
1253
  id: ID!
683
1254
  functionID: ID!
1255
+ "True while the original invocation is unfinished. Query functionInvocation with this ID; do not start a new invocation to check its outcome."
1256
+ pending: Boolean!
684
1257
  ok: Boolean!
685
1258
  result: JSON
686
1259
  stdout: String!
@@ -696,7 +1269,7 @@ enum CurrentActorKind {
696
1269
  OWNER
697
1270
  "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
1271
  AGENT
699
- "A human signed in via GitHub OAuth, who manages their linked agents and apps."
1272
+ "A human signed in via an enabled identity provider, who manages their linked agents and apps."
700
1273
  USER
701
1274
  }
702
1275
 
@@ -714,20 +1287,115 @@ type CurrentActorResources {
714
1287
  }
715
1288
 
716
1289
  """
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.
1290
+ Recorded compute for the caller's raw owner ID in the current UTC calendar
1291
+ month. Linked API identities retain separate ownership and are not included.
1292
+ This is metering, not an invoice: no prices or charges are calculated. Running
1293
+ VMs may await an hourly checkpoint; builds, storage, network and provider API
1294
+ usage are not included. Null means the ledger could not be read.
723
1295
  """
724
1296
  type CurrentActorCompute {
725
1297
  seconds: Float!
726
1298
  mibSeconds: Float!
727
1299
  since: String!
728
1300
  until: String!
1301
+ "When this ledger snapshot was read, in UTC."
1302
+ generatedAt: String!
1303
+ "Latest recorded activity in the period, not a completeness watermark."
1304
+ lastRecordedAt: String
1305
+ "Corrupt, conflicting, or memory-less records make this report incomplete."
1306
+ incomplete: Boolean!
1307
+ byMode: [ComputeModeUsage!]!
729
1308
  }
730
1309
 
1310
+ type ComputeModeUsage {
1311
+ mode: String!
1312
+ seconds: Float!
1313
+ mibSeconds: Float!
1314
+ }
1315
+
1316
+ "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."
1317
+ type Agent {
1318
+ id: ID!
1319
+ ownerId: ID!
1320
+ name: String!
1321
+ "The one-line role under the name."
1322
+ job: String!
1323
+ "Standing rules the harness is given on every run; never sent as a task."
1324
+ description: String!
1325
+ "The agent's own workspace (hidden from the apps lists): its env and secrets, the home of its computer."
1326
+ workspaceID: ID!
1327
+ "The alias of the agent's computer (its SSH user name); null until it exists."
1328
+ boxAlias: String
1329
+ "The agent's computer: the newest BOX deployment in its workspace (BUILDING while it starts, FAILED when its build failed)."
1330
+ computer: Deployment
1331
+ "The harness a run of this agent uses; null means the node's first catalogue entry."
1332
+ harness: String
1333
+ "The provider connection backing its runs; null means the account's per-harness setting."
1334
+ connectionID: ID
1335
+ "Set when the referenced connection still exists: its display name and kind."
1336
+ connectionName: String
1337
+ kind: String
1338
+ model: String
1339
+ "read-only or read-write: what a run of this agent may do."
1340
+ access: String!
1341
+ "\"human\" routes the harness's guarded actions to a person; empty keeps the harness's defaults."
1342
+ approval: String!
1343
+ "The workspaces the agent works on; the first supplies a run's files and env until runs execute on the agent's computer."
1344
+ scopeWorkspaceIDs: [ID!]!
1345
+ "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:*))."
1346
+ allowedTools: [String!]!
1347
+ "Derived on the server from the newest run and the computer, so every surface agrees."
1348
+ state: AgentState!
1349
+ "One line about the state: the current task, the pending question, or what failed."
1350
+ stateDetail: String!
1351
+ "The agent's newest run, whoever messaged it."
1352
+ lastRun: Run
1353
+ createdAt: String!
1354
+ updatedAt: String!
1355
+ }
1356
+ "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)."
1357
+ enum AgentState {
1358
+ STARTING
1359
+ IDLE
1360
+ WORKING
1361
+ NEEDS_YOU
1362
+ ASLEEP
1363
+ FAILED
1364
+ }
1365
+ type AgentPayload {
1366
+ agent: Agent!
1367
+ }
1368
+ input CreateAgentInput {
1369
+ name: String!
1370
+ job: String
1371
+ description: String
1372
+ harness: String
1373
+ connectionID: ID
1374
+ model: String
1375
+ access: String
1376
+ approval: String
1377
+ scopeWorkspaceIDs: [ID!]
1378
+ "The computer's size; omitted uses the platform defaults."
1379
+ boxResources: BoxResourcesInput
1380
+ }
1381
+ input UpdateAgentInput {
1382
+ id: ID!
1383
+ name: String
1384
+ job: String
1385
+ description: String
1386
+ harness: String
1387
+ connectionID: ID
1388
+ model: String
1389
+ access: String
1390
+ approval: String
1391
+ "Replaces the scope when given; an empty list clears it."
1392
+ scopeWorkspaceIDs: [ID!]
1393
+ "Replaces the remembered permission rules when given; an empty list forgets them all."
1394
+ allowedTools: [String!]
1395
+ }
1396
+ input DeleteAgentInput {
1397
+ id: ID!
1398
+ }
731
1399
  enum RunStatus {
732
1400
  QUEUED
733
1401
  RUNNING
@@ -753,13 +1421,22 @@ that executed one task and was destroyed when it exited. Deployments answer
753
1421
  "what is running"; runs answer "what happened".
754
1422
  """
755
1423
  type Run {
1424
+ "Source workspace and its current alias holders, when still visible."
1425
+ source: RunSource
1426
+ events(limit: Int = 100): [RunEvent!]
1427
+ console(tailBytes: Int = 16384): RunConsole
1428
+ transcript(tailBytes: Int = 16384): RunTranscript
756
1429
  id: ID!
757
1430
  workspaceID: ID!
1431
+ "The agent this run is a task of (docs/agents-design.md); null for a plain app run."
1432
+ agentID: ID
758
1433
  ownerId: ID!
759
1434
  status: RunStatus!
760
1435
  rawStatus: String!
761
1436
  command: String
762
1437
  harness: String
1438
+ "The preset this run was created from (display-only; resolved at creation)."
1439
+ preset: String
763
1440
  prompt: String
764
1441
  setup: [String!]!
765
1442
  skills: [String!]!
@@ -800,6 +1477,19 @@ type Run {
800
1477
  settledAt: String
801
1478
  }
802
1479
 
1480
+ "A terminal source summary: run navigation cannot recursively expand runs."
1481
+ type RunSource {
1482
+ id: ID!
1483
+ name: String!
1484
+ deployments: [RunSourceDeployment!]!
1485
+ }
1486
+
1487
+ type RunSourceDeployment {
1488
+ id: ID!
1489
+ alias: String
1490
+ createdAt: String!
1491
+ }
1492
+
803
1493
  "One bound integration's contribution to a run. env is the credential set injected: empty (none), read-only or read-write."
804
1494
  type RunIntegration {
805
1495
  kind: String!
@@ -892,6 +1582,8 @@ type CurrentActorAgent {
892
1582
  claimedAt: String
893
1583
  "Deployments owned by this linked agent. Resolved lazily — only fetched when selected."
894
1584
  deployments: [Deployment!]!
1585
+ "Every app this agent serves right now, across its workspaces: what deleteLinkedAgent takes offline, and the exact list its confirmAliases must name."
1586
+ liveApps: [LiveApp!]!
895
1587
  }
896
1588
 
897
1589
  type CurrentActor {
@@ -914,11 +1606,160 @@ type CurrentActor {
914
1606
  linkedAgents: [CurrentActorAgent!]!
915
1607
  }
916
1608
 
1609
+ "The authenticated account. Nested fields can be delivered independently with @defer."
1610
+ type Viewer {
1611
+ availableIntegrations: [IntegrationKind!]
1612
+ integrationConnections: [IntegrationConnection!]
1613
+ integrationSubscriptions: [IntegrationSubscription!]
1614
+ integrationBindings: [IntegrationBinding!]
1615
+ customDomains: [CustomDomain!]
1616
+ }
1617
+
1618
+
1619
+ "Current app placement without assessing destinations or granting placement authority."
1620
+ type PlacementDisplay {
1621
+ current: PlacementCurrent
1622
+ target: PlacementServer
1623
+ blockers: [PlacementBlocker!]!
1624
+ }
1625
+ "Public metadata for the exact server currently assigned to the app."
1626
+ type PlacementServer {
1627
+ id: ID!
1628
+ "CONTROL_PLANE or WORKER."
1629
+ kind: String!
1630
+ name: String!
1631
+ "Empty when the server has no configured region."
1632
+ region: String!
1633
+ incarnation: String!
1634
+ }
1635
+
1636
+ "Read-only placement metadata. Blockers are observations, never permission to move data."
1637
+ type PlacementCatalog {
1638
+ current: PlacementCurrent
1639
+ targets: [PlacementTarget!]!
1640
+ "Regions advertised by the caller's targets, before the optional region filter."
1641
+ regions: [String!]!
1642
+ blockers: [PlacementBlocker!]!
1643
+ "Where a new app would land. Set only when no current app is selected."
1644
+ newApps: PlacementDecision
1645
+ }
1646
+ "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."
1647
+ type PlacementDecision {
1648
+ serverID: String!
1649
+ serverName: String!
1650
+ "CONTROL_PLANE or WORKER."
1651
+ kind: String!
1652
+ region: String!
1653
+ "request, account_default, default or platform."
1654
+ source: String!
1655
+ "The server that was asked for when the app cannot use it; empty otherwise."
1656
+ requested: String!
1657
+ "Why the app stays on the control plane, e.g. worker_disconnected or static_on_control_plane; empty when it lands as chosen."
1658
+ code: String!
1659
+ message: String!
1660
+ }
1661
+ type PlacementTarget {
1662
+ id: ID!
1663
+ "CONTROL_PLANE or WORKER."
1664
+ kind: String!
1665
+ name: String!
1666
+ "Empty when the server has no configured region."
1667
+ region: String!
1668
+ "Exact installation identity; empty when it is unknown for the default server."
1669
+ incarnation: String!
1670
+ status: String!
1671
+ connected: Boolean!
1672
+ "An authenticated session on a settled runtime release; not a data-admission grant."
1673
+ ready: Boolean!
1674
+ deployBlockers: [PlacementBlocker!]!
1675
+ moveBlockers: [PlacementBlocker!]!
1676
+ }
1677
+ type PlacementBlocker { code: String!, message: String! }
1678
+ type PlacementCurrent {
1679
+ deploymentID: ID!
1680
+ alias: String!
1681
+ aliasRevision: String!
1682
+ "Empty if the exact current placement is unknown."
1683
+ targetID: String!
1684
+ incarnation: String!
1685
+ state: String!
1686
+ blockers: [PlacementBlocker!]!
1687
+ }
1688
+ input PlacementTargetsInput {
1689
+ workspaceID: ID
1690
+ alias: String
1691
+ deploymentID: ID
1692
+ "Filter exact servers by region; this does not select a regional scheduler."
1693
+ region: String
1694
+ }
1695
+
1696
+ type PlacementMoveApp { deploymentID: ID!, workspaceID: ID!, alias: String! }
1697
+ type PlacementMovePlan {
1698
+ deploymentID: ID!
1699
+ "Public retry key; empty for an older operator-created move."
1700
+ idempotencyKey: String!
1701
+ planDigest: String!
1702
+ targetID: String!
1703
+ targetIncarnation: String!
1704
+ affectedApps: [PlacementMoveApp!]!
1705
+ resourceCount: Int!
1706
+ }
1707
+ type PlacementMove {
1708
+ id: ID!
1709
+ phase: String!
1710
+ complete: Boolean!
1711
+ running: Boolean!
1712
+ resumable: Boolean!
1713
+ plan: PlacementMovePlan!
1714
+ }
1715
+ input PlacementMovePlanInput {
1716
+ deploymentID: ID!
1717
+ targetID: String!
1718
+ targetIncarnation: String!
1719
+ idempotencyKey: String!
1720
+ }
1721
+ input BeginPlacementMoveInput {
1722
+ deploymentID: ID!
1723
+ targetID: String!
1724
+ targetIncarnation: String!
1725
+ idempotencyKey: String!
1726
+ planDigest: String!
1727
+ }
1728
+ input PlacementMoveStatusInput {
1729
+ deploymentID: ID!
1730
+ idempotencyKey: String
1731
+ moveID: ID
1732
+ }
1733
+ input RunPlacementMoveInput {
1734
+ deploymentID: ID!
1735
+ idempotencyKey: String
1736
+ moveID: ID!
1737
+ }
1738
+
917
1739
  type Query {
1740
+ "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."
1741
+ placementMovePlan(input: PlacementMovePlanInput!): PlacementMovePlan!
1742
+ "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."
1743
+ placementMove(input: PlacementMoveStatusInput!): PlacementMove
1744
+
1745
+ "Authoritative current server for a visible app, without assessing destination eligibility."
1746
+ deploymentPlacement(deploymentID: ID!): PlacementDisplay
1747
+ "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."
1748
+ placementTargets(input: PlacementTargetsInput): PlacementCatalog!
1749
+ "Where a new app would land for the caller, or for the owner of an owned workspace. Read-only."
1750
+ newAppPlacement(workspaceID: ID): PlacementDecision!
1751
+
1752
+ viewer: Viewer
918
1753
  whoami: CurrentActor!
919
1754
  functions: [Function!]!
920
1755
  functionsPage(input: PageInput): FunctionPage!
921
1756
  function(id: ID!): Function
1757
+ "Read the caller's saved invocation without executing it again. Requires deploy scope, as invoking does."
1758
+ functionInvocation(id: ID!): Invocation!
1759
+ "Read one visible registration without contacting a worker or starting work."
1760
+ functionRegistration(id: ID!): FunctionRegistration!
1761
+ "Discover saved registrations, including a lost initial response. Ordered by immutable ID; limit 1–100."
1762
+ functionRegistrations(after: ID, limit: Int = 20): FunctionRegistrationPage!
922
1763
  "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
1764
  workspaces: [Workspace!]!
924
1765
  "Paginated sources visible to the current account, with the same ownership scope as workspaces."
@@ -932,8 +1773,14 @@ type Query {
932
1773
  myTeamInvites: [TeamInviteForMe!]!
933
1774
  "The signed-in owner's BYO custom domains, oldest first."
934
1775
  customDomains: [CustomDomain!]!
1776
+ "The apps a custom domain of yours may move to (moveCustomDomain), by alias."
1777
+ customDomainMoveTargets(domain: String!): [CustomDomainMoveTarget!]!
935
1778
  "The signed-in account's SSH public keys — the keys that may open its boxes."
936
1779
  sshKeys: [SSHKey!]!
1780
+ "The sign-in providers the platform offers, and which of them are linked to the signed-in account."
1781
+ signInMethods: [SignInMethod!]!
1782
+ "Computers whose git is signed in to platform git with the signed-in account, newest first."
1783
+ gitSignIns: [GitSignIn!]!
937
1784
  "Integration types available to connect (postgres, redis, …)."
938
1785
  availableIntegrations: [IntegrationKind!]!
939
1786
  "The signed-in owner's connected backends."
@@ -942,6 +1789,8 @@ type Query {
942
1789
  projectSharedConnections(projectID: ID!): [IntegrationConnection!]!
943
1790
  "The signed-in owner's app bindings (which app uses which backend)."
944
1791
  integrationBindings: [IntegrationBinding!]!
1792
+ "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."
1793
+ integrationBindingUsage(id: ID!): IntegrationUsage!
945
1794
  "Account-level event subscriptions, including disabled state."
946
1795
  integrationSubscriptions: [IntegrationSubscription!]!
947
1796
  "Secret-free configuration effective for this app's next deploy or lifecycle event."
@@ -950,6 +1799,10 @@ type Query {
950
1799
  publishDomainProbe(bindingID: ID!): PublishDomainProbe!
951
1800
  workspaceFiles(workspaceID: ID!): [WorkspaceFile!]!
952
1801
  workspaceFilesPage(input: ListWorkspaceFilesInput!): WorkspaceFilePage!
1802
+ "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."
1803
+ appCommits(workspaceID: ID!, limit: Int, before: String): AppCommitPage!
1804
+ "One commit of an app with platform git with its diff against its first parent."
1805
+ appCommit(workspaceID: ID!, sha: String!): AppCommitDiff!
953
1806
  readWorkspaceFile(input: ReadWorkspaceFileInput!): WorkspaceFileContent
954
1807
  workspaceEnv(workspaceID: ID!): [WorkspaceEnvVar!]!
955
1808
  workspaceEnvPage(input: ListWorkspaceEnvInput!): WorkspaceEnvPage!
@@ -974,10 +1827,25 @@ type Query {
974
1827
  deploymentEventsPage(input: ListDeploymentEventsInput!): DeploymentEventPage!
975
1828
  "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
1829
  runHarnesses: [RunHarness!]!
1830
+ "The caller's chosen model-provider connection per harness. A run of that harness receives its env unless the workspace sets the same name."
1831
+ runHarnessSettings: [RunHarnessSetting!]!
1832
+ "The caller's named run presets, sorted by name."
1833
+ runPresets: [RunPreset!]!
1834
+ "The caller's run triggers, sorted by name."
1835
+ runTriggers: [RunTrigger!]!
1836
+ "One trigger's shared secret — shown to its owner to configure the sending side."
1837
+ runTriggerSecret(input: RunTriggerIDInput!): String!
1838
+ "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)."
1839
+ runHarnessProviderChoices: [RunHarnessProviderChoice!]!
1840
+ "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."
1841
+ runHarnessProviderModels(harness: String!, connectionID: ID!): [String!]!
977
1842
  "One agent run visible to the caller; not-found otherwise."
978
1843
  run(id: ID!): Run!
979
1844
  "The caller's agent runs, newest first."
980
1845
  runs(input: ListRunsInput): RunPage!
1846
+ "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."
1847
+ agents: [Agent!]!
1848
+ agent(id: ID!): Agent!
981
1849
  "A run's own history, oldest first (at most limit, default 100)."
982
1850
  runEvents(runID: ID!, limit: Int): [RunEvent!]!
983
1851
  "The live console tail of a RUNNING run (tailBytes, default 16384)."
@@ -994,8 +1862,19 @@ type Query {
994
1862
  }
995
1863
 
996
1864
  type Mutation {
1865
+ "Persist the exact previewed move before execution. Retry with the same key/digest after a lost response."
1866
+ beginPlacementMove(input: BeginPlacementMoveInput!): PlacementMove!
1867
+ "Execute or resume the saved move. On interruption, read status and retry this same ID; cancellation does not undo it."
1868
+ runPlacementMove(input: RunPlacementMoveInput!): PlacementMove!
1869
+
997
1870
  "Start an agent run; returns once its VM has started (RUNNING) or FAILED with error."
998
1871
  createRun(input: CreateRunInput!): Run!
1872
+ "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."
1873
+ createAgent(input: CreateAgentInput!): AgentPayload!
1874
+ "Change an agent's setup; omitted fields stay as they are. Owner only."
1875
+ updateAgent(input: UpdateAgentInput!): AgentPayload!
1876
+ "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."
1877
+ deleteAgent(input: DeleteAgentInput!): DeleteResultPayload!
999
1878
  "Stop an agent run (idempotent on an already-cancelled run)."
1000
1879
  cancelRun(id: ID!): Run!
1001
1880
  "Answer a BLOCKED run's pending question; the run resumes."
@@ -1003,7 +1882,11 @@ type Mutation {
1003
1882
  "Continue a finished run: a new run in the same workspace with the parent's context prepended to the prompt."
1004
1883
  followUpRun(input: FollowUpRunInput!): Run!
1005
1884
  registerFunction(input: RegisterFunctionInput!): Function!
1885
+ "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."
1886
+ resumeFunctionRegistration(id: ID!): FunctionRegistration!
1006
1887
  invokeFunction(input: InvokeFunctionInput!): Invocation!
1888
+ "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."
1889
+ deleteFunction(input: DeleteFunctionInput!): DeleteResultPayload!
1007
1890
  createWorkspace(input: CreateWorkspaceInput!): CreateWorkspacePayload!
1008
1891
  "Rename an app (its workspace). Does not change its alias or public URL."
1009
1892
  renameWorkspace(input: RenameWorkspaceInput!): Workspace!
@@ -1041,10 +1924,28 @@ type Mutation {
1041
1924
  connectIntegration(input: ConnectIntegrationInput!): IntegrationConnectionPayload!
1042
1925
  "Start the link flow for a link integration (e.g. Telegram); returns a URL to open."
1043
1926
  startIntegrationLink(kind: String!): IntegrationLinkPayload!
1927
+ "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."
1928
+ startConnectionAuthorization(input: IntegrationIDInput!): IntegrationLinkPayload!
1929
+ "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."
1930
+ setRunHarnessProvider(input: SetRunHarnessProviderInput!): RunHarnessSettingPayload!
1931
+ "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."
1932
+ saveRunPreset(input: SaveRunPresetInput!): RunPresetPayload!
1933
+ "Forget a preset by name; runs already created from it are untouched."
1934
+ deleteRunPreset(input: DeleteRunPresetInput!): DeleteResultPayload!
1935
+ "Create or update a run trigger. The template's workspace must be visible to the caller; a named preset must exist."
1936
+ saveRunTrigger(input: SaveRunTriggerInput!): RunTriggerPayload!
1937
+ "Forget a trigger and its queued firings."
1938
+ deleteRunTrigger(input: RunTriggerIDInput!): DeleteResultPayload!
1939
+ "Forget the caller's chosen provider for a harness; it falls back to no credential."
1940
+ clearRunHarnessProvider(input: ClearRunHarnessProviderInput!): DeleteResultPayload!
1941
+ "Revoke (best effort, at the server) and forget a connection's OAuth grant; runs stop getting the server."
1942
+ revokeConnectionAuthorization(input: IntegrationIDInput!): DeleteResultPayload!
1044
1943
  "Delete a connected backend by id. Active bindings must be unbound first; detached bindings retain it until rollback/provider cleanup completes."
1045
1944
  deleteIntegrationConnection(input: IntegrationIDInput!): DeleteResultPayload!
1046
1945
  "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
1946
  bindIntegration(input: BindIntegrationInput!): IntegrationBindingPayload!
1947
+ "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."
1948
+ revealIntegrationEnv(input: IntegrationIDInput!): IntegrationEnvReveal!
1048
1949
  "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
1950
  setPublishDomain(input: PublishDomainInput!): IntegrationBindingPayload!
1050
1951
  "Detach the publish binding's custom hostname. The site stays reachable at its provider URL."
@@ -1076,6 +1977,8 @@ type Mutation {
1076
1977
  addCustomDomain(input: AddCustomDomainInput!): CustomDomain!
1077
1978
  "Detach a custom domain; the app keeps serving on its alias/URL."
1078
1979
  removeCustomDomain(input: RemoveCustomDomainInput!): DeleteResultPayload!
1980
+ "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."
1981
+ moveCustomDomain(input: MoveCustomDomainInput!): CustomDomain!
1079
1982
  writeWorkspaceTextFile(input: WriteWorkspaceTextFileInput!): WorkspaceFilePayload!
1080
1983
  writeWorkspaceBase64File(input: WriteWorkspaceBase64FileInput!): WorkspaceFilePayload!
1081
1984
  uploadWorkspaceFile(input: UploadWorkspaceFileInput!): WorkspaceFilePayload!
@@ -1098,31 +2001,44 @@ type Mutation {
1098
2001
  adoptDeploymentAlias(input: AdoptDeploymentAliasInput!): CreateDeploymentPayload!
1099
2002
  "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
2003
  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."
2004
+ "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
2005
  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."
2006
+ "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
2007
  connectRepo(input: ConnectRepoInput!): ImportRepoPayload!
1105
2008
  syncRepo(input: SyncRepoInput!): ImportRepoPayload!
2009
+ "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."
2010
+ restoreAppVersion(input: RestoreAppVersionInput!): RestoreAppVersionPayload!
2011
+ "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."
2012
+ moveAppToGitHub(input: MoveAppToGitHubInput!): MoveAppToGitHubPayload!
2013
+ "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."
2014
+ enablePlatformGit(input: EnablePlatformGitInput!): EnablePlatformGitPayload!
1106
2015
  unlinkRepo(input: UnlinkRepoInput!): DeleteResultPayload!
1107
2016
  installRepoWebhook(input: InstallRepoWebhookInput!): ImportRepoPayload!
1108
2017
  setRepoDeployAlias(input: SetRepoDeployAliasInput!): ImportRepoPayload!
2018
+ "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."
2019
+ addRepoEnvironment(input: AddRepoEnvironmentInput!): AddRepoEnvironmentPayload!
2020
+ "Replace the encrypted repository token; empty removes it. Does not deploy. GitHub then uses the account token. Other providers use anonymous access."
2021
+ setRepoToken(workspaceID: ID!, token: String!): ImportRepoPayload!
1109
2022
  setGitHubToken(input: SetGitHubTokenInput!): GitHubTokenStatus!
1110
2023
  deleteGitHubToken: GitHubTokenStatus!
1111
2024
  restartService(input: ServiceControlInput!): ServiceRuntimeStatus!
1112
2025
  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."
2026
+ "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."
2027
+ deleteApp(input: DeleteAppInput!): DeleteAppPayload!
2028
+ "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
2029
  deleteDeployment(input: DeleteDeploymentInput!): DeleteResultPayload!
1115
2030
  "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
2031
  rollbackDeployment(input: RollbackDeploymentInput!): Deployment!
2032
+ "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
2033
  deleteWorkspace(input: DeleteWorkspaceInput!): DeleteResultPayload!
1118
2034
  setAppState(input: SetAppStateInput!): AppStateValue!
1119
2035
  deleteAppState(input: DeleteAppStateInput!): DeleteResultPayload!
1120
2036
  "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!
2037
+ renameLinkedAgent(input: RenameLinkedAgentInput!): CurrentActorAgent!
2038
+ "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."
2039
+ deleteLinkedAgent(input: DeleteLinkedAgentInput!): DeleteLinkedAgentPayload!
1124
2040
  "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!
2041
+ rotateLinkedAgentToken(input: RotateLinkedAgentTokenInput!): LinkedAgentTokenPayload!
1126
2042
  "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
2043
  createTelegramLinkCode: TelegramLinkPayload!
1128
2044
  }
@@ -1138,21 +2054,40 @@ type TelegramLinkPayload {
1138
2054
  botUsername: String!
1139
2055
  }
1140
2056
 
1141
- input RenameAgentInput {
2057
+ input RenameLinkedAgentInput {
1142
2058
  agentID: ID!
1143
2059
  name: String!
1144
2060
  }
1145
2061
 
1146
- input DeleteAgentInput {
2062
+ input DeleteLinkedAgentInput {
1147
2063
  agentID: ID!
2064
+ "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."
2065
+ confirmAliases: [String!]
2066
+ }
2067
+
2068
+ "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."
2069
+ type DeleteLinkedAgentPayload {
2070
+ ok: Boolean!
2071
+ "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."
2072
+ affectedApps: [LinkedAgentAffectedApp!]!
2073
+ "Affected apps you cannot see, counted but not named."
2074
+ hiddenAffectedApps: Int!
1148
2075
  }
1149
2076
 
1150
- input RotateAgentTokenInput {
2077
+ "An app that lost a binding of a deleted agent's connection."
2078
+ type LinkedAgentAffectedApp {
2079
+ workspaceID: ID!
2080
+ alias: String!
2081
+ "The integration kind of the binding it lost."
2082
+ kind: String!
2083
+ }
2084
+
2085
+ input RotateLinkedAgentTokenInput {
1151
2086
  agentID: ID!
1152
2087
  }
1153
2088
 
1154
2089
  "A newly issued agent credential. Shown once: only its hash is stored."
1155
- type AgentTokenPayload {
2090
+ type LinkedAgentTokenPayload {
1156
2091
  "The agent this token authenticates as — unchanged by rotation."
1157
2092
  agentID: ID!
1158
2093
  "The bearer token. Copy it now; it cannot be read again."
@@ -1171,10 +2106,16 @@ input RegisterFunctionInput {
1171
2106
  }
1172
2107
 
1173
2108
  input InvokeFunctionInput {
2109
+ "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."
2110
+ invocationID: ID
1174
2111
  functionID: ID!
1175
2112
  payload: JSON
1176
2113
  }
1177
2114
 
2115
+ input DeleteFunctionInput {
2116
+ functionID: ID!
2117
+ }
2118
+
1178
2119
  input PageInput {
1179
2120
  limit: Int
1180
2121
  offset: Int
@@ -1198,12 +2139,16 @@ input WriteWorkspaceTextFileInput {
1198
2139
  workspaceID: ID!
1199
2140
  path: String!
1200
2141
  text: String!
2142
+ "On an app with platform git: the message of the commit this write makes; default names the file. Ignored on other apps."
2143
+ commitMessage: String
1201
2144
  }
1202
2145
 
1203
2146
  input WriteWorkspaceBase64FileInput {
1204
2147
  workspaceID: ID!
1205
2148
  path: String!
1206
2149
  contentBase64: String!
2150
+ "On an app with platform git: the message of the commit this write makes; default names the file. Ignored on other apps."
2151
+ commitMessage: String
1207
2152
  }
1208
2153
 
1209
2154
  input UploadWorkspaceFileInput {
@@ -1214,6 +2159,32 @@ input UploadWorkspaceFileInput {
1214
2159
 
1215
2160
  type WorkspaceFilePayload {
1216
2161
  file: WorkspaceFile!
2162
+ "The commit the write made on an app with platform git; null on any other app, and for an upload."
2163
+ commit: FileCommit
2164
+ }
2165
+
2166
+ "The commit a file write made on an app with platform git."
2167
+ type FileCommit {
2168
+ "The commit; with committed false, the branch head that already held exactly these files. Null while it waits."
2169
+ sha: String
2170
+ branch: String!
2171
+ message: String!
2172
+ "False when git already held exactly these files: no commit was made."
2173
+ committed: Boolean!
2174
+ "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."
2175
+ waiting: Boolean!
2176
+ "The app's commits waiting, while this one does."
2177
+ waitingCount: Int!
2178
+ "While waiting, the commit's place in the app's commit queue: it has landed once AppCommitPage.waitingCommits is 0, or AppCommitPage.landedSeq reaches it."
2179
+ queueSeq: Int
2180
+ "Whether the branch head, this write included, is imported and deployed like a push."
2181
+ deploys: Boolean!
2182
+ "Paths a push changed too since the write was made: the head keeps the pushed version (sha is the merge). Empty otherwise."
2183
+ conflicts: [String!]!
2184
+ "The write as it was made, the merge's second parent, when there are conflicts."
2185
+ writeSHA: String
2186
+ "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."
2187
+ paused: Boolean!
1217
2188
  }
1218
2189
 
1219
2190
  input ReadWorkspaceFileInput {
@@ -1350,15 +2321,17 @@ input CreateDeploymentInput {
1350
2321
  cronPath: String
1351
2322
  "BOX only: optional VM size. Omit for the platform defaults."
1352
2323
  boxResources: BoxResourcesInput
2324
+ "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."
2325
+ server: String
1353
2326
  }
1354
2327
 
1355
2328
  "A box's requested VM size. Omitted fields use the platform default."
1356
2329
  input BoxResourcesInput {
1357
- "RAM in MiB (default 2048, min 512, max 16384)."
2330
+ "RAM in MiB (default 4096, min 512, max 16384)."
1358
2331
  memMiB: Int
1359
- "vCPU count (default 2, max 8)."
2332
+ "vCPU count (default 4, max 16)."
1360
2333
  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."
2334
+ "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
2335
  diskGiB: Int
1363
2336
  }
1364
2337
 
@@ -1380,6 +2353,8 @@ input DeployWorkspaceInput {
1380
2353
  serviceMode: ServiceMode
1381
2354
  "STATIC only; overrides the detected SPA fallback."
1382
2355
  spaMode: Boolean
2356
+ "Server for a NEW app (see CreateDeploymentInput.server); overrides unfazed.json server."
2357
+ server: String
1383
2358
  }
1384
2359
 
1385
2360
  type CreateDeploymentPayload {
@@ -1387,6 +2362,11 @@ type CreateDeploymentPayload {
1387
2362
  }
1388
2363
 
1389
2364
  input ImportRepoInput {
2365
+ "Git provider, default github. Forgejo accepts any public HTTPS instance."
2366
+ provider: String
2367
+ serverURL: String
2368
+ "Optional repository token, encrypted per workspace; never returned."
2369
+ token: String
1390
2370
  repo: String!
1391
2371
  ref: String
1392
2372
  alias: String
@@ -1397,6 +2377,9 @@ input ImportRepoInput {
1397
2377
  }
1398
2378
 
1399
2379
  input ConnectRepoInput {
2380
+ provider: String
2381
+ serverURL: String
2382
+ token: String
1400
2383
  workspaceID: ID!
1401
2384
  repo: String!
1402
2385
  ref: String
@@ -1409,6 +2392,8 @@ input ConnectRepoInput {
1409
2392
  input SyncRepoInput {
1410
2393
  workspaceID: ID!
1411
2394
  force: Boolean
2395
+ "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."
2396
+ restoreApps: [String!]
1412
2397
  }
1413
2398
 
1414
2399
  input UnlinkRepoInput {
@@ -1425,6 +2410,26 @@ input SetRepoDeployAliasInput {
1425
2410
  alias: String!
1426
2411
  }
1427
2412
 
2413
+ input AddRepoEnvironmentInput {
2414
+ "The main app's git-linked workspace."
2415
+ workspaceID: ID!
2416
+ "Environment name such as staging: 1-20 lowercase letters, digits or dashes. Every alias of the environment ends with -<name>."
2417
+ name: String!
2418
+ "The branch the environment deploys on every push."
2419
+ branch: String!
2420
+ "Single-app alias; defaults to <main alias>-<name>."
2421
+ alias: String
2422
+ }
2423
+
2424
+ type AddRepoEnvironmentPayload {
2425
+ workspace: Workspace!
2426
+ gitSource: GitSource
2427
+ "The main app's secret names that were not copied; set them on the environment."
2428
+ skippedSecrets: [String!]!
2429
+ "Why the environment's push webhook could not be installed automatically."
2430
+ webhookError: String
2431
+ }
2432
+
1428
2433
  input SetGitHubTokenInput {
1429
2434
  token: String!
1430
2435
  }
@@ -1446,9 +2451,14 @@ input ListDeploymentsInput {
1446
2451
  }
1447
2452
 
1448
2453
  input CreateRunInput {
1449
- workspaceID: ID!
2454
+ "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."
2455
+ workspaceID: ID
2456
+ "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."
2457
+ agentID: ID
1450
2458
  prompt: String
1451
2459
  harness: String
2460
+ "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."
2461
+ preset: String
1452
2462
  command: String
1453
2463
  "Shell commands run in /work before the harness; the first failure fails the run."
1454
2464
  setup: [String!]
@@ -1483,6 +2493,7 @@ input FollowUpRunInput {
1483
2493
 
1484
2494
  input ListRunsInput {
1485
2495
  workspaceID: ID
2496
+ agentID: ID
1486
2497
  status: RunStatus
1487
2498
  limit: Int
1488
2499
  offset: Int
@@ -1515,8 +2526,32 @@ input RollbackDeploymentInput {
1515
2526
 
1516
2527
  input DeleteWorkspaceInput {
1517
2528
  workspaceID: ID!
1518
- "Also delete every deployment snapshotted from the workspace (stopping service VMs) instead of refusing while any exist."
2529
+ "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
2530
  withDeployments: Boolean
2531
+ "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."
2532
+ confirmAliases: [String!]
2533
+ }
2534
+
2535
+ input DeleteAppInput {
2536
+ workspaceID: ID!
2537
+ "The app to delete: an alias of the workspace."
2538
+ alias: String!
2539
+ }
2540
+
2541
+ "What deleteApp removed."
2542
+ type DeleteAppPayload {
2543
+ ok: Boolean!
2544
+ alias: String!
2545
+ "Deployment records deleted: the live release, retained releases and failed or building attempts."
2546
+ deployments: Int!
2547
+ "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."
2548
+ domains: [String!]!
2549
+ "App-scoped env, secret and REMOVE entries removed."
2550
+ envEntries: Int!
2551
+ "Whether pushes and syncs of this git-linked workspace now skip the alias while unfazed.json apps[] declares it."
2552
+ skippedOnSync: Boolean!
2553
+ "The workspace's apps that are still live, untouched."
2554
+ otherApps: [String!]!
1520
2555
  }
1521
2556
 
1522
2557
  input SetAppStateInput {
@@ -1530,3 +2565,642 @@ input DeleteAppStateInput {
1530
2565
  alias: String!
1531
2566
  key: String!
1532
2567
  }
2568
+
2569
+ "Application feedback shared by the on-page toolbar, dashboard and agents."
2570
+ type ReviewAttachment {
2571
+ id: ID!
2572
+ filename: String!
2573
+ mimeType: String!
2574
+ size: Int!
2575
+ }
2576
+ type ReviewMessage {
2577
+ id: ID!
2578
+ author: String!
2579
+ "Current authenticated profile picture, empty for guests and accounts without one."
2580
+ avatarURL: String!
2581
+ guest: Boolean!
2582
+ body: String!
2583
+ deleted: Boolean!
2584
+ "Whether the current viewer may delete this message."
2585
+ canDelete: Boolean!
2586
+ createdAt: String!
2587
+ attachments: [ReviewAttachment!]!
2588
+ }
2589
+ type ReviewThread {
2590
+ id: ID!
2591
+ "Permanent discussion number within this app generation, including resolved discussions."
2592
+ number: Int!
2593
+ workspaceID: ID!
2594
+ alias: String!
2595
+ deploymentID: ID!
2596
+ page: String!
2597
+ title: String!
2598
+ selector: String!
2599
+ selectedText: String!
2600
+ x: Float!
2601
+ y: Float!
2602
+ viewportWidth: Int!
2603
+ viewportHeight: Int!
2604
+ userAgent: String!
2605
+ resolved: Boolean!
2606
+ createdAt: String!
2607
+ messages: [ReviewMessage!]!
2608
+ }
2609
+ type ReviewPage { threads: [ReviewThread!]! total: Int! canManage: Boolean! }
2610
+ type ReviewLink { id: ID! url: String createdAt: String! expiresAt: String! revoked: Boolean! }
2611
+ type ReviewFile { filename: String! mimeType: String! contentBase64: String! }
2612
+ type ReviewNotificationConnection { id: ID! name: String! }
2613
+ type ReviewNotificationChannel {
2614
+ kind: String!
2615
+ label: String!
2616
+ iconURL: String!
2617
+ connectMethod: String!
2618
+ connections: [ReviewNotificationConnection!]!
2619
+ }
2620
+ type ReviewNotificationSettings {
2621
+ enabled: Boolean!
2622
+ appEnabled: Boolean!
2623
+ connected: Boolean!
2624
+ connectionID: String!
2625
+ channels: [ReviewNotificationChannel!]!
2626
+ }
2627
+ input ReviewAttachmentInput { filename: String! contentBase64: String! }
2628
+ input ReviewCommentInput {
2629
+ deploymentID: ID!
2630
+ threadID: ID
2631
+ body: String!
2632
+ page: String
2633
+ title: String
2634
+ selector: String
2635
+ selectedText: String
2636
+ x: Float
2637
+ y: Float
2638
+ viewportWidth: Int
2639
+ viewportHeight: Int
2640
+ userAgent: String
2641
+ requestID: ID
2642
+ attachments: [ReviewAttachmentInput!]
2643
+ }
2644
+ extend type Query {
2645
+ "Personal feedback notification settings. Omit deploymentID for the account-wide switch."
2646
+ reviewNotificationSettings(deploymentID: ID): ReviewNotificationSettings!
2647
+ reviewThreads(deploymentID: ID!, offset: Int, limit: Int): ReviewPage!
2648
+ reviewThread(deploymentID: ID!, threadID: ID!): ReviewThread!
2649
+ reviewLinks(deploymentID: ID!): [ReviewLink!]!
2650
+ reviewAttachment(deploymentID: ID!, threadID: ID!, attachmentID: ID!): ReviewFile!
2651
+ }
2652
+ extend type Mutation {
2653
+ "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."
2654
+ setReviewNotificationSettings(deploymentID: ID, connectionID: ID, enabled: Boolean!): ReviewNotificationSettings!
2655
+ "Create a revocable 30-day guest link, or a one-hour personal toolbar session."
2656
+ createReviewLink(deploymentID: ID!, personal: Boolean = false): ReviewLink!
2657
+ revokeReviewLink(deploymentID: ID!, linkID: ID!): Boolean!
2658
+ addReviewComment(input: ReviewCommentInput!): ReviewThread!
2659
+ "Delete one comment and its attachments. Authors may delete their own; the app owner may moderate all comments."
2660
+ deleteReviewComment(deploymentID: ID!, threadID: ID!, messageID: ID!): Boolean!
2661
+ resolveReviewThread(deploymentID: ID!, threadID: ID!, resolved: Boolean!): ReviewThread!
2662
+ }
2663
+
2664
+ "A commit of an app's platform git repository."
2665
+ type AppCommit {
2666
+ sha: String!
2667
+ "The commit message (its first 64 KiB)."
2668
+ message: String!
2669
+ authorName: String!
2670
+ authorEmail: String!
2671
+ "When the change was authored (RFC3339)."
2672
+ authoredAt: String!
2673
+ "Paths changed against the first parent (every path of a first commit)."
2674
+ filesChanged: Int!
2675
+ "Aliases whose live release runs this commit."
2676
+ deployedTo: [String!]!
2677
+ }
2678
+
2679
+ type AppCommitPage {
2680
+ branch: String!
2681
+ "The branch head; null while the branch has no commits."
2682
+ head: String
2683
+ commits: [AppCommit!]!
2684
+ "Pass as before for the older commits; null on the last page."
2685
+ nextBefore: String
2686
+ "Whether pushes (and restores) deploy the app."
2687
+ autoDeploy: Boolean!
2688
+ "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."
2689
+ waitingCommits: Int!
2690
+ "When the oldest waiting write was made (RFC3339); null when none waits."
2691
+ waitingSince: String
2692
+ "While writes wait, the queue seq of the commit that last landed: a write whose FileCommit.queueSeq is at most this one is done. Null when none landed since the queue was last empty."
2693
+ landedSeq: Int
2694
+ "The commit every live alias of the app runs, whether or not it is on this page."
2695
+ deployed: [AppDeployedCommit!]!
2696
+ }
2697
+
2698
+ "The commit one live alias runs (AppCommitPage.deployed)."
2699
+ type AppDeployedCommit {
2700
+ alias: String!
2701
+ sha: String!
2702
+ "The release serving the alias."
2703
+ deploymentID: ID!
2704
+ }
2705
+
2706
+ type AppCommitFile {
2707
+ path: String!
2708
+ "added, modified, deleted or type changed."
2709
+ status: String!
2710
+ }
2711
+
2712
+ type AppCommitDiff {
2713
+ commit: AppCommit!
2714
+ files: [AppCommitFile!]!
2715
+ "Unified diff against the first parent, at most 64 KiB (16 KiB per file); lines starting with … say what was left out."
2716
+ patch: String!
2717
+ truncated: Boolean!
2718
+ additions: Int!
2719
+ deletions: Int!
2720
+ }
2721
+
2722
+ input RestoreAppVersionInput {
2723
+ workspaceID: ID!
2724
+ "Full 40-character sha of the commit whose files the app gets back."
2725
+ sha: String!
2726
+ "When set, refuse (changing nothing) unless the branch head is still this commit."
2727
+ expectedHead: String
2728
+ "Message of the new commit; default Restore <short sha> (<its message>)."
2729
+ message: String
2730
+ }
2731
+
2732
+ input MoveAppToGitHubInput {
2733
+ workspaceID: ID!
2734
+ "GitHub account or organization to create the repository in; default the caller's own GitHub account."
2735
+ owner: String
2736
+ "Name of the new repository; default the app's alias."
2737
+ repo: String
2738
+ "When set, refuse (changing nothing) unless the branch head is still this commit."
2739
+ expectedHead: String
2740
+ }
2741
+
2742
+ input EnablePlatformGitInput {
2743
+ workspaceID: ID!
2744
+ }
2745
+
2746
+ type EnablePlatformGitPayload {
2747
+ "The app's source, now deploying from its platform repository."
2748
+ workspace: Workspace!
2749
+ "The repository by alias (https://git.unfz.to/<alias>.git)."
2750
+ cloneURL: String!
2751
+ "The repository by workspace ID, which never changes."
2752
+ stableCloneURL: String!
2753
+ branch: String!
2754
+ "The commit holding the app's files; null for an app without files, whose first push fills it."
2755
+ commitSHA: String
2756
+ "False when the repository already held exactly these files."
2757
+ committed: Boolean!
2758
+ }
2759
+
2760
+ type MoveAppToGitHubPayload {
2761
+ "owner/name of the GitHub repository."
2762
+ repository: String!
2763
+ repoURL: String!
2764
+ "False when an earlier attempt of this move created the repository, or the caller named an empty one."
2765
+ created: Boolean!
2766
+ "The app had moved before: nothing was done."
2767
+ alreadyMoved: Boolean!
2768
+ "The branch head the app deploys, on GitHub too."
2769
+ head: String!
2770
+ "Whether every push to the branch on GitHub deploys the app."
2771
+ webhookInstalled: Boolean!
2772
+ "The platform repository, kept read-only."
2773
+ platformCloneURL: String!
2774
+ "What else happened, such as a push that landed during the move."
2775
+ note: String
2776
+ }
2777
+
2778
+ type RestoreAppVersionPayload {
2779
+ "The new commit; with committed false, the head that already holds that version."
2780
+ sha: String!
2781
+ committed: Boolean!
2782
+ "Whether the new commit is deployed like a push. The deploy is an import queued behind the restore, with no deployment id yet: waitForDeployment(workspaceID, gitCommit: sha, alias: deployAlias, after: waitAfter) waits for it."
2783
+ deploys: Boolean!
2784
+ message: String!
2785
+ "The workspace restored."
2786
+ workspaceID: ID!
2787
+ "The app's branch."
2788
+ branch: String!
2789
+ "The alias a push deploys to, when the link names one; null otherwise (unfazed.json apps[] deploys each of its aliases)."
2790
+ deployAlias: String
2791
+ "The commit restored from (the sha asked for)."
2792
+ restoredFrom: String!
2793
+ "The branch head the new commit was made on; null with committed false."
2794
+ parent: String
2795
+ "Where the platform's deployment order stood when the restore began: waitForDeployment's after, so an earlier deploy of the same files never answers. A place in that order, not a time, so a clock change cannot hide the restore's deploy."
2796
+ waitAfter: Int!
2797
+ }
2798
+
2799
+ enum WorkflowRunStatus { PENDING RUNNING COMPLETED FAILED CANCELLED }
2800
+
2801
+ type WorkflowValue { text: String! bytes: Int! truncated: Boolean! error: Boolean! }
2802
+ type WorkflowError { message: String! stack: String code: String truncated: Boolean! }
2803
+ type WorkflowAttribute { key: String! value: String! }
2804
+
2805
+ type WorkflowRunSummary {
2806
+ runID: ID! workflowName: String! displayName: String! module: String!
2807
+ status: WorkflowRunStatus! deploymentID: String!
2808
+ createdAt: String! startedAt: String completedAt: String updatedAt: String! expiredAt: String
2809
+ errorCode: String attributes: [WorkflowAttribute!]!
2810
+ }
2811
+ type WorkflowRunPage { runs: [WorkflowRunSummary!]! nextCursor: String hasMore: Boolean! }
2812
+
2813
+ type WorkflowStep {
2814
+ stepID: ID! stepName: String! displayName: String! module: String!
2815
+ status: String! attempt: Int!
2816
+ createdAt: String! startedAt: String completedAt: String updatedAt: String! retryAfter: String
2817
+ }
2818
+ type WorkflowStepPage { steps: [WorkflowStep!]! nextCursor: String hasMore: Boolean! }
2819
+ type WorkflowWait { correlationID: ID! status: String! createdAt: String! resumeAt: String completedAt: String }
2820
+ type WorkflowHook {
2821
+ hookID: ID! kind: String! status: String! createdAt: String!
2822
+ receivedCount: Int! lastReceivedAt: String disposedAt: String
2823
+ }
2824
+ type WorkflowStreamInfo { name: String! tailIndex: Int done: Boolean }
2825
+ type WorkflowEvent {
2826
+ eventID: ID! eventType: String! correlationID: String createdAt: String!
2827
+ info: JSON! infoTruncated: Boolean!
2828
+ }
2829
+ type WorkflowEventPage { events: [WorkflowEvent!]! nextCursor: String hasMore: Boolean! }
2830
+
2831
+ type WorkflowRun {
2832
+ run: WorkflowRunSummary!
2833
+ input: WorkflowValue output: WorkflowValue error: WorkflowError
2834
+ steps: [WorkflowStep!]! stepsNextCursor: String stepsHasMore: Boolean!
2835
+ waits: [WorkflowWait!]! hooks: [WorkflowHook!]! streams: [WorkflowStreamInfo!]!
2836
+ eventsScanned: Int! eventsComplete: Boolean!
2837
+ waitsTruncated: Boolean! hooksTruncated: Boolean!
2838
+ }
2839
+ type WorkflowStepDetail {
2840
+ step: WorkflowStep! input: WorkflowValue output: WorkflowValue error: WorkflowError
2841
+ events: [WorkflowEvent!]! eventsHasMore: Boolean!
2842
+ }
2843
+ type WorkflowEventDetail { event: WorkflowEvent! data: WorkflowValue }
2844
+ type WorkflowStreamChunk { index: Int! bytes: Int! encoding: String! text: String! truncated: Boolean! }
2845
+ type WorkflowStreamPage {
2846
+ name: String! chunks: [WorkflowStreamChunk!]! nextCursor: String hasMore: Boolean!
2847
+ done: Boolean! tailIndex: Int!
2848
+ }
2849
+ type WorkflowInfo { version: Int! package: String! workflow: String! specVersion: Int workflows: [WorkflowName!]! }
2850
+ type WorkflowName { name: String! displayName: String! module: String! }
2851
+
2852
+ "The app's queue counters, read from its data node (docs/workflow-world.md, Counters)."
2853
+ type WorkflowQueueCounters {
2854
+ "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."
2855
+ ready: Int!
2856
+ "Ready messages whose due time has passed, waiting for a delivery slot."
2857
+ due: Int!
2858
+ "Messages being delivered to the app now."
2859
+ leased: Int!
2860
+ "Messages given up after 24 h of failed deliveries."
2861
+ dead: Int!
2862
+ "The earliest due time of a due message; null when none is due (delayed messages do not count)."
2863
+ oldestDueAt: String
2864
+ "Bytes stored and charged: live messages, idempotency keys and dead messages."
2865
+ bytes: Int!
2866
+ "Dead messages dropped over the dead-letter cap, oldest first."
2867
+ deadEvicted: Int!
2868
+ }
2869
+ type WorkflowDeadMessage {
2870
+ messageID: ID! queueName: String! contentType: String! createdAt: String! deadAt: String!
2871
+ attempt: Int! deliveryCount: Int! size: Int! payloadPreview: String
2872
+ }
2873
+ type WorkflowQueue { counters: WorkflowQueueCounters! dead: [WorkflowDeadMessage!]! deadNextCursor: String deadUnavailable: Boolean! }
2874
+
2875
+ extend type Query {
2876
+ workflowInfo(alias: String!): WorkflowInfo!
2877
+ workflowRuns(alias: String!, status: [WorkflowRunStatus!], workflowName: String, limit: Int, cursor: String): WorkflowRunPage!
2878
+ workflowRun(alias: String!, runID: ID!): WorkflowRun!
2879
+ workflowSteps(alias: String!, runID: ID!, limit: Int, cursor: String): WorkflowStepPage!
2880
+ workflowStep(alias: String!, runID: ID!, stepID: ID!): WorkflowStepDetail!
2881
+ workflowEvents(alias: String!, runID: ID!, correlationID: String, newestFirst: Boolean, limit: Int, cursor: String): WorkflowEventPage!
2882
+ workflowEvent(alias: String!, runID: ID!, eventID: ID!): WorkflowEventDetail!
2883
+ workflowStream(alias: String!, runID: ID!, name: String!, limit: Int, cursor: String): WorkflowStreamPage!
2884
+ workflowQueue(alias: String!, deadLimit: Int, deadCursor: String): WorkflowQueue!
2885
+ }
2886
+
2887
+ input WakeWorkflowRunInput { alias: String! runID: ID! correlationIDs: [ID!] }
2888
+ input CancelWorkflowRunInput { alias: String! runID: ID! reason: String }
2889
+ input ReplayDeadWorkflowMessagesInput { alias: String! messageIDs: [ID!] all: Boolean }
2890
+ type WakeWorkflowRunPayload { stoppedCount: Int! }
2891
+ type CancelWorkflowRunPayload { runID: ID! status: WorkflowRunStatus! }
2892
+ type ReplayDeadWorkflowMessagesPayload { replayed: Int! skipped: Int! remainingDead: Int! }
2893
+
2894
+ extend type Mutation {
2895
+ wakeWorkflowRun(input: WakeWorkflowRunInput!): WakeWorkflowRunPayload!
2896
+ cancelWorkflowRun(input: CancelWorkflowRunInput!): CancelWorkflowRunPayload!
2897
+ replayDeadWorkflowMessages(input: ReplayDeadWorkflowMessagesInput!): ReplayDeadWorkflowMessagesPayload!
2898
+ }
2899
+
2900
+ "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."
2901
+ type DeletedPiece {
2902
+ id: ID!
2903
+ kind: String!
2904
+ "Where it is held: control-plane, app-state:<namespace>, box-disk:<workspace>, provider:<kind>."
2905
+ host: String!
2906
+ location: String!
2907
+ bytes: Float!
2908
+ "Why it is no longer held (expired, erasure, budget, restored); null while it is."
2909
+ closed: String
2910
+ closedAt: String
2911
+ }
2912
+
2913
+ "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."
2914
+ type DeletedItem {
2915
+ deletionID: ID!
2916
+ "app, workspace, release, run_agent or linked_agent."
2917
+ subjectKind: String!
2918
+ subjectID: String!
2919
+ what: String!
2920
+ workspaceID: ID
2921
+ workspaceName: String
2922
+ aliases: [String!]!
2923
+ "Custom domains a restore serves again."
2924
+ domains: [String!]!
2925
+ ownerID: String
2926
+ deletedAt: String!
2927
+ deletedBy: String
2928
+ "The surface the delete came through: mcp, graphql, form, api, system."
2929
+ via: String!
2930
+ "delete_app, delete_workspace, delete_deployment, git_retired, delete_agent, delete_linked_agent."
2931
+ path: String!
2932
+ "When everything it holds is erased for good."
2933
+ purgeAfter: String!
2934
+ "held, restoring, restored or purged."
2935
+ state: String!
2936
+ restoredAt: String
2937
+ restoredBy: String
2938
+ "Bytes it holds on the control plane."
2939
+ bytes: Float!
2940
+ pieces: [DeletedPiece!]!
2941
+ "Whether restoreDeleted can bring it back now; refusal says why not."
2942
+ restorable: Boolean!
2943
+ refusal: String
2944
+ "Other open deletions of the same subject (a delete finished by deleting again later) that restoring this one brings back with it."
2945
+ restoresWith: [ID!]!
2946
+ "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."
2947
+ related: [ID!]!
2948
+ }
2949
+
2950
+ "What restoreDeleted brought back."
2951
+ type DeletedRestorePayload {
2952
+ deletionID: ID!
2953
+ "restored, or already_restored for one restored before (its apps were started again)."
2954
+ status: String!
2955
+ workspaceID: ID
2956
+ releases: Int!
2957
+ apps: [String!]!
2958
+ domains: [String!]!
2959
+ bindings: [String!]!
2960
+ "App env entries set again since the delete, which kept their current values."
2961
+ envKept: [String!]!
2962
+ "Where each restored app serves, or why it did not start yet."
2963
+ placement: [String!]!
2964
+ "Other deletions of the same subject restored with it."
2965
+ with: [ID!]!
2966
+ }
2967
+
2968
+ "What purgeDeleted erased."
2969
+ type DeletedPurgePayload {
2970
+ deletionID: ID!
2971
+ purged: Int!
2972
+ bytes: Float!
2973
+ }
2974
+
2975
+ extend type Query {
2976
+ "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."
2977
+ deletedItems(
2978
+ "Only deletions in this state: held, restoring, restored or purged."
2979
+ state: String
2980
+ "Matching deletions to skip, for the next page."
2981
+ offset: Int
2982
+ "At most this many, newest first (default 50, at most 500)."
2983
+ limit: Int
2984
+ ): [DeletedItem!]!
2985
+ }
2986
+
2987
+ extend type Mutation {
2988
+ "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."
2989
+ restoreDeleted(deletionID: ID!): DeletedRestorePayload!
2990
+ "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."
2991
+ purgeDeleted(deletionID: ID!, reason: String!): DeletedPurgePayload!
2992
+ }
2993
+
2994
+ "One file of writeWorkspaceFiles, as MCP write_workspace_files takes it."
2995
+ input WorkspaceFileWriteInput {
2996
+ "Relative path, such as server.ts or assets/logo.png."
2997
+ path: String!
2998
+ "The file: UTF-8 text, or base64-encoded bytes with encoding base64."
2999
+ content: String!
3000
+ "text (the default) or base64."
3001
+ encoding: String
3002
+ }
3003
+
3004
+ input WriteWorkspaceFilesInput {
3005
+ workspaceID: ID!
3006
+ "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."
3007
+ files: [WorkspaceFileWriteInput!]!
3008
+ "On an app with platform git: the message of the call's ONE commit; default names the files. Ignored on other apps."
3009
+ commitMessage: String
3010
+ }
3011
+
3012
+ "One file's outcome in writeWorkspaceFiles, in the order given."
3013
+ type WorkspaceFileWriteResult {
3014
+ path: String!
3015
+ ok: Boolean!
3016
+ "The written file; null when this path failed."
3017
+ file: WorkspaceFile
3018
+ "Why this path failed; null when it was written."
3019
+ error: String
3020
+ }
3021
+
3022
+ "What writeWorkspaceFiles wrote: every file's outcome, and the one commit it made."
3023
+ type WriteWorkspaceFilesPayload {
3024
+ workspaceID: ID!
3025
+ files: [WorkspaceFileWriteResult!]!
3026
+ written: Int!
3027
+ failed: Int!
3028
+ "Bytes written, decoded."
3029
+ totalBytes: Int!
3030
+ "The ONE commit the whole call made on an app with platform git (before any file was written); null on any other app."
3031
+ commit: FileCommit
3032
+ }
3033
+
3034
+ "What waitForDeployment read when it stopped waiting (MCP wait_for_deployment)."
3035
+ type DeploymentWaitPayload {
3036
+ "The deployment as the wait left it; null only when waiting by gitCommit before the import made it."
3037
+ deployment: Deployment
3038
+ "The deployment's status; waiting by gitCommit before the import made it, IMPORTING, or FAILED once the import failed on the commit."
3039
+ status: String!
3040
+ "Waiting by gitCommit: the workspace's latest git import, IMPORTING, READY or FAILED."
3041
+ importStatus: String
3042
+ "Waiting by gitCommit: that import's error, from the git provider and the build — untrusted text."
3043
+ importError: String
3044
+ "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."
3045
+ timedOut: Boolean!
3046
+ "How long the server waited, in milliseconds."
3047
+ waitedMs: Int!
3048
+ "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."
3049
+ placement: JSON
3050
+ }
3051
+
3052
+ "One error group of a service app (MCP list_errors). Its title, type, culprit and level are what the running app reported: untrusted text."
3053
+ type AppErrorGroup {
3054
+ id: ID!
3055
+ title: String!
3056
+ type: String
3057
+ culprit: String
3058
+ level: String!
3059
+ "open, resolved or muted."
3060
+ status: String!
3061
+ "Every occurrence, stored or sampled out."
3062
+ count: Int!
3063
+ "Occurrences kept whole (the first and the newest 50; fewer past the rate limit)."
3064
+ storedEvents: Int!
3065
+ firstSeen: String!
3066
+ lastSeen: String!
3067
+ firstDeploymentID: String
3068
+ firstGitCommit: String
3069
+ lastDeploymentID: String
3070
+ lastGitCommit: String
3071
+ "It stays resolved while this deployment reports it; another deployment regresses it."
3072
+ resolvedInDeploymentID: String
3073
+ resolvedAt: String
3074
+ mutedAt: String
3075
+ regressedAt: String
3076
+ }
3077
+
3078
+ type AppErrorGroupPage {
3079
+ groups: [AppErrorGroup!]!
3080
+ "Pass as cursor for the next page; null on the last."
3081
+ nextCursor: String
3082
+ "Why the list may be empty for another reason than no errors (an older server, an app that opted out); null otherwise."
3083
+ note: String
3084
+ }
3085
+
3086
+ "One stored occurrence of an error group."
3087
+ type AppErrorEventRef {
3088
+ eventID: String!
3089
+ at: String!
3090
+ deploymentID: String
3091
+ level: String!
3092
+ }
3093
+
3094
+ "One deployment an error group was seen in."
3095
+ type AppErrorDeployment {
3096
+ deploymentID: String!
3097
+ gitCommit: String
3098
+ "The app's current release."
3099
+ current: Boolean!
3100
+ count: Int!
3101
+ firstSeen: String!
3102
+ lastSeen: String!
3103
+ }
3104
+
3105
+ "One error group with its latest occurrence (MCP get_error_group). Everything the app reported is untrusted text."
3106
+ type AppErrorGroupDetail {
3107
+ group: AppErrorGroup!
3108
+ "The newest stored occurrence, whole, as a Sentry event."
3109
+ latestEvent: JSON
3110
+ firstEvent: AppErrorEventRef
3111
+ recentEvents: [AppErrorEventRef!]!
3112
+ "The deployments it was seen in, most recent first."
3113
+ deployments: [AppErrorDeployment!]!
3114
+ "The platform saw the latest occurrence end its VM's process: the crash is in serviceStatus."
3115
+ fatal: Boolean!
3116
+ "The VM whose console carried the latest occurrence; null for what an SDK reported."
3117
+ vmID: String
3118
+ }
3119
+
3120
+ input AppErrorGroupInput {
3121
+ workspaceID: ID!
3122
+ alias: String!
3123
+ "The group's id (AppErrorGroup.id, or group_id of an error.new or error.regressed event)."
3124
+ groupID: ID!
3125
+ }
3126
+
3127
+ "An event type pollEvents and waitForEvents read (MCP list_event_types)."
3128
+ type PlatformEventType {
3129
+ name: String!
3130
+ description: String!
3131
+ delivery: [String!]!
3132
+ "Its filters, as a JSON Schema: pass them in arguments under the type's name."
3133
+ inputSchema: JSON!
3134
+ "Its payload (data), as a JSON Schema."
3135
+ payloadSchema: JSON!
3136
+ }
3137
+
3138
+ "Which events to read (MCP poll_events)."
3139
+ input PlatformEventsInput {
3140
+ "Event types; omit for every type you may receive."
3141
+ names: [String!]
3142
+ "Filters per type, keyed by its name, e.g. {\"deployment.finished\": {\"alias\": \"shop\", \"status\": \"FAILED\"}}."
3143
+ arguments: JSON
3144
+ "Where to continue; omit to start from now (nothing is replayed)."
3145
+ cursor: String
3146
+ "At most this many events."
3147
+ max: Int
3148
+ }
3149
+
3150
+ "One event (deployment.finished, service.crashed, error.new, git.conflict, …). Its data is minimal: ids, statuses, excerpts."
3151
+ type PlatformEvent {
3152
+ eventID: ID!
3153
+ name: String!
3154
+ timestamp: String!
3155
+ data: JSON!
3156
+ }
3157
+
3158
+ type PlatformEventPage {
3159
+ events: [PlatformEvent!]!
3160
+ "Pass back as cursor: at once while hasMore, otherwise after nextPollMs (or to waitForEvents)."
3161
+ cursor: String!
3162
+ "Events may be missing (a cursor older than the history, or a control-plane crash): re-read state."
3163
+ truncated: Boolean!
3164
+ hasMore: Boolean!
3165
+ nextPollMs: Int!
3166
+ "waitForEvents only: the budget ended the wait with no event. Call again with cursor."
3167
+ timedOut: Boolean!
3168
+ }
3169
+
3170
+ extend type Query {
3171
+ """
3172
+ Block until a deployment leaves BUILDING (and a new app's direct landing
3173
+ ends) or timeoutSeconds pass (default 45, at most 50), then return it — the
3174
+ server-side wait of MCP wait_for_deployment (the same wait), woken by the
3175
+ deploy pipeline itself, so a client never polls. A timeout is not an error:
3176
+ timedOut is true and the deployment is the current state; call again to keep
3177
+ waiting. Give id, or workspaceID with gitCommit for the deploy a git import
3178
+ makes of that commit (a restore, a push or a sync deploys before any id
3179
+ exists): it waits for the import to make it, then for it, within the same
3180
+ budget. alias is required when several apps deployed the commit; after
3181
+ (RestoreAppVersionPayload.waitAfter) counts only deployments begun after it,
3182
+ so an earlier deploy of the same commit never answers.
3183
+ """
3184
+ waitForDeployment(id: ID, workspaceID: ID, gitCommit: String, alias: String, after: Int, timeoutSeconds: Int): DeploymentWaitPayload!
3185
+ "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."
3186
+ appErrors(workspaceID: ID!, alias: String!, status: String, limit: Int, cursor: String): AppErrorGroupPage!
3187
+ "One error group of a service app with its latest occurrence, recent occurrences and the deployments it was seen in (MCP get_error_group)."
3188
+ appErrorGroup(workspaceID: ID!, alias: String!, groupID: ID!): AppErrorGroupDetail!
3189
+ "The event types pollEvents and waitForEvents read, with each type's filters and payload (MCP list_event_types)."
3190
+ platformEventTypes: [PlatformEventType!]!
3191
+ "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."
3192
+ pollEvents(input: PlatformEventsInput): PlatformEventPage!
3193
+ "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."
3194
+ waitForEvents(input: PlatformEventsInput, timeoutSeconds: Int): PlatformEventPage!
3195
+ }
3196
+
3197
+ extend type Mutation {
3198
+ "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."
3199
+ writeWorkspaceFiles(input: WriteWorkspaceFilesInput!): WriteWorkspaceFilesPayload!
3200
+ "Mark an error group resolved in the app's current deployment, once its fix is deployed (MCP resolve_error_group). Requires deploy scope."
3201
+ resolveAppErrorGroup(input: AppErrorGroupInput!): AppErrorGroup!
3202
+ "Mute an expected error group: still counted, never regresses or sends events (MCP mute_error_group). Requires deploy scope."
3203
+ muteAppErrorGroup(input: AppErrorGroupInput!): AppErrorGroup!
3204
+ "Open a resolved or muted error group again (MCP reopen_error_group). Requires deploy scope."
3205
+ reopenAppErrorGroup(input: AppErrorGroupInput!): AppErrorGroup!
3206
+ }