@owlmeans/viable-common 0.0.45 → 0.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +163 -13
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/agent-presentation/SKILL.md +1 -1
  4. package/agent-meta/skills/viable-common/SKILL.md +132 -23
  5. package/build/connect/account/types.d.ts +81 -0
  6. package/build/connect/account/types.d.ts.map +1 -0
  7. package/build/connect/account/types.js +2 -0
  8. package/build/connect/account/types.js.map +1 -0
  9. package/build/connect/branding/types.d.ts +26 -4
  10. package/build/connect/branding/types.d.ts.map +1 -1
  11. package/build/connect/call/types.d.ts +28 -0
  12. package/build/connect/call/types.d.ts.map +1 -0
  13. package/build/connect/call/types.js +2 -0
  14. package/build/connect/call/types.js.map +1 -0
  15. package/build/connect/config/types.d.ts +45 -0
  16. package/build/connect/config/types.d.ts.map +1 -0
  17. package/build/connect/config/types.js +2 -0
  18. package/build/connect/config/types.js.map +1 -0
  19. package/build/connect/consts.d.ts +383 -1
  20. package/build/connect/consts.d.ts.map +1 -1
  21. package/build/connect/consts.js +387 -1
  22. package/build/connect/consts.js.map +1 -1
  23. package/build/connect/conversion/types.d.ts +24 -0
  24. package/build/connect/conversion/types.d.ts.map +1 -1
  25. package/build/connect/entrypoints/types.d.ts +22 -13
  26. package/build/connect/entrypoints/types.d.ts.map +1 -1
  27. package/build/connect/entrypoints.d.ts +441 -8
  28. package/build/connect/entrypoints.d.ts.map +1 -1
  29. package/build/connect/entrypoints.js +292 -9
  30. package/build/connect/entrypoints.js.map +1 -1
  31. package/build/connect/errors.d.ts +12 -0
  32. package/build/connect/errors.d.ts.map +1 -1
  33. package/build/connect/errors.js +16 -0
  34. package/build/connect/errors.js.map +1 -1
  35. package/build/connect/feed/types.d.ts +71 -0
  36. package/build/connect/feed/types.d.ts.map +1 -0
  37. package/build/connect/feed/types.js +2 -0
  38. package/build/connect/feed/types.js.map +1 -0
  39. package/build/connect/files/types.d.ts +34 -0
  40. package/build/connect/files/types.d.ts.map +1 -0
  41. package/build/connect/files/types.js +2 -0
  42. package/build/connect/files/types.js.map +1 -0
  43. package/build/connect/git/types.d.ts +170 -0
  44. package/build/connect/git/types.d.ts.map +1 -0
  45. package/build/connect/git/types.js +14 -0
  46. package/build/connect/git/types.js.map +1 -0
  47. package/build/connect/iam/types.d.ts +25 -0
  48. package/build/connect/iam/types.d.ts.map +1 -0
  49. package/build/connect/iam/types.js +8 -0
  50. package/build/connect/iam/types.js.map +1 -0
  51. package/build/connect/index.d.ts +9 -0
  52. package/build/connect/index.d.ts.map +1 -1
  53. package/build/connect/ops.d.ts +189 -0
  54. package/build/connect/ops.d.ts.map +1 -0
  55. package/build/connect/ops.js +2 -0
  56. package/build/connect/ops.js.map +1 -0
  57. package/build/connect/production/types.d.ts +65 -0
  58. package/build/connect/production/types.d.ts.map +1 -0
  59. package/build/connect/production/types.js +2 -0
  60. package/build/connect/production/types.js.map +1 -0
  61. package/build/connect/project/types.d.ts +23 -16
  62. package/build/connect/project/types.d.ts.map +1 -1
  63. package/build/connect/references/types.d.ts +424 -2
  64. package/build/connect/references/types.d.ts.map +1 -1
  65. package/build/connect/references.d.ts.map +1 -1
  66. package/build/connect/references.js +122 -0
  67. package/build/connect/references.js.map +1 -1
  68. package/build/connect/schemas.d.ts +65 -2
  69. package/build/connect/schemas.d.ts.map +1 -1
  70. package/build/connect/schemas.js +306 -1
  71. package/build/connect/schemas.js.map +1 -1
  72. package/build/connect/settings/types.d.ts +0 -25
  73. package/build/connect/settings/types.d.ts.map +1 -1
  74. package/build/connect/slot/types.d.ts +24 -0
  75. package/build/connect/slot/types.d.ts.map +1 -0
  76. package/build/connect/slot/types.js +2 -0
  77. package/build/connect/slot/types.js.map +1 -0
  78. package/build/connect/types.d.ts +453 -0
  79. package/build/connect/types.d.ts.map +1 -0
  80. package/build/connect/types.js +2 -0
  81. package/build/connect/types.js.map +1 -0
  82. package/build/convert/record/types.d.ts +0 -9
  83. package/build/convert/record/types.d.ts.map +1 -1
  84. package/build/convert/schemas.d.ts +2 -5
  85. package/build/convert/schemas.d.ts.map +1 -1
  86. package/build/convert/schemas.js +2 -15
  87. package/build/convert/schemas.js.map +1 -1
  88. package/build/iam-console/consts.d.ts +13 -0
  89. package/build/iam-console/consts.d.ts.map +1 -0
  90. package/build/iam-console/consts.js +13 -0
  91. package/build/iam-console/consts.js.map +1 -0
  92. package/build/iam-console/consts.local.d.ts +123 -0
  93. package/build/iam-console/consts.local.d.ts.map +1 -0
  94. package/build/iam-console/consts.local.js +50 -0
  95. package/build/iam-console/consts.local.js.map +1 -0
  96. package/build/iam-console/index.d.ts +4 -0
  97. package/build/iam-console/index.d.ts.map +1 -0
  98. package/build/iam-console/index.js +3 -0
  99. package/build/iam-console/index.js.map +1 -0
  100. package/build/iam-console/schemas.d.ts +38 -0
  101. package/build/iam-console/schemas.d.ts.map +1 -0
  102. package/build/iam-console/schemas.js +217 -0
  103. package/build/iam-console/schemas.js.map +1 -0
  104. package/build/iam-console/types.d.ts +140 -0
  105. package/build/iam-console/types.d.ts.map +1 -0
  106. package/build/iam-console/types.js +2 -0
  107. package/build/iam-console/types.js.map +1 -0
  108. package/build/index.d.ts +2 -0
  109. package/build/index.d.ts.map +1 -1
  110. package/build/index.js +2 -0
  111. package/build/index.js.map +1 -1
  112. package/build/intent/flow.d.ts +17 -0
  113. package/build/intent/flow.d.ts.map +1 -0
  114. package/build/intent/flow.js +65 -0
  115. package/build/intent/flow.js.map +1 -0
  116. package/build/legal/consts.d.ts +67 -0
  117. package/build/legal/consts.d.ts.map +1 -0
  118. package/build/legal/consts.js +24 -0
  119. package/build/legal/consts.js.map +1 -0
  120. package/build/legal/index.d.ts +3 -0
  121. package/build/legal/index.d.ts.map +1 -0
  122. package/build/legal/index.js +2 -0
  123. package/build/legal/index.js.map +1 -0
  124. package/build/legal/types.d.ts +17 -0
  125. package/build/legal/types.d.ts.map +1 -0
  126. package/build/legal/types.js +2 -0
  127. package/build/legal/types.js.map +1 -0
  128. package/build/planning/flows.d.ts +34 -0
  129. package/build/planning/flows.d.ts.map +1 -0
  130. package/build/planning/flows.js +174 -0
  131. package/build/planning/flows.js.map +1 -0
  132. package/build/planning/schemas.d.ts.map +1 -1
  133. package/build/planning/schemas.js +0 -1
  134. package/build/planning/schemas.js.map +1 -1
  135. package/build/planning/types.d.ts +0 -2
  136. package/build/planning/types.d.ts.map +1 -1
  137. package/build/skills/roles.d.ts +43 -0
  138. package/build/skills/roles.d.ts.map +1 -0
  139. package/build/skills/roles.js +44 -0
  140. package/build/skills/roles.js.map +1 -0
  141. package/package.json +19 -3
  142. package/src/connect/account/types.ts +91 -0
  143. package/src/connect/branding/types.ts +28 -4
  144. package/src/connect/call/types.ts +31 -0
  145. package/src/connect/config/types.ts +51 -0
  146. package/src/connect/consts.ts +400 -1
  147. package/src/connect/conversion/types.ts +25 -0
  148. package/src/connect/entrypoints/types.ts +22 -7
  149. package/src/connect/entrypoints.ts +592 -15
  150. package/src/connect/errors.ts +18 -0
  151. package/src/connect/feed/types.ts +76 -0
  152. package/src/connect/files/types.ts +38 -0
  153. package/src/connect/git/types.ts +187 -0
  154. package/src/connect/iam/types.ts +28 -0
  155. package/src/connect/index.ts +9 -0
  156. package/src/connect/production/types.ts +71 -0
  157. package/src/connect/project/types.ts +25 -12
  158. package/src/connect/references/types.ts +166 -5
  159. package/src/connect/references.ts +210 -5
  160. package/src/connect/schemas.ts +366 -5
  161. package/src/connect/settings/types.ts +0 -25
  162. package/src/connect/slot/types.ts +24 -0
  163. package/src/convert/record/types.ts +0 -11
  164. package/src/convert/schemas.ts +3 -19
  165. package/src/iam-console/consts.local.ts +64 -0
  166. package/src/iam-console/consts.ts +15 -0
  167. package/src/iam-console/index.ts +3 -0
  168. package/src/iam-console/schemas.ts +251 -0
  169. package/src/iam-console/types.ts +165 -0
  170. package/src/index.ts +2 -0
  171. package/src/legal/consts.ts +25 -0
  172. package/src/legal/index.ts +2 -0
  173. package/src/legal/types.ts +22 -0
  174. package/src/planning/schemas.ts +0 -1
  175. package/src/planning/types.ts +0 -2
  176. package/tests/connect-convert.spec.ts +6 -2
  177. package/tests/connect-entrypoints.spec.ts +554 -16
  178. package/tests/error-status.spec.ts +2 -1
  179. package/tests/iam-console.spec.ts +89 -0
  180. package/tests/planning.spec.ts +8 -0
@@ -1,33 +1,87 @@
1
1
  import { contract, openProtocol, protocol, typed } from '@owlmeans/entrypoint'
2
2
  import { route, RouteMethod } from '@owlmeans/route'
3
- import { connect } from './consts.js'
3
+ import { connect, ConnectPaidGate } from './consts.js'
4
4
  import {
5
- ConnectAttachBodySchema, ConnectConfirmBodySchema, ConnectConvertCreateBodySchema,
5
+ ConnectAccessTokenParamsSchema, ConnectIntentPickupBodySchema, ConnectLlmBodySchema, ConnectPrivacyWithdrawBodySchema,
6
+ ConnectProjectLlmBodySchema, ConnectAttachBodySchema, ConnectBrandingCreditBodySchema, ConnectCallCollectParamsSchema, ConnectConfigSaveBodySchema, ConnectCallCollectQuerySchema, ConnectConfirmBodySchema, ConnectConvertCreateBodySchema,
6
7
  ConnectConvertProceedBodySchema, ConnectConvertStartBodySchema, ConnectCreateBodySchema,
8
+ ConnectFileMetaQuerySchema, ConnectFileQuerySchema, ConnectFileSaveBodySchema,
7
9
  ConnectInquiryParamsSchema, ConnectKitApplyBodySchema,
8
- ConnectModifyBodySchema, ConnectOpParamsSchema, ConnectOpResultSchema,
10
+ ConnectModifyBodySchema, ConnectRenameBodySchema, ConnectOpParamsSchema, ConnectOpResultSchema,
9
11
  ConnectPipelineParamsSchema, ConnectPipelineResumeBodySchema, ConnectProjectBrandingSaveSchema,
10
12
  ConnectProjectIdSchema, ConnectSessionOpenSchema, ConnectSessionParamsSchema, ConnectPullQuerySchema,
11
- ConnectStoryParamsSchema, InquiryAnswerSchema,
13
+ ConnectOrganizationBrandingSaveSchema, ConnectScopeQuerySchema, ConnectStoryParamsSchema, InquiryAnswerSchema,
14
+ ConnectGitCommitBodySchema, ConnectGitRevertBodySchema, ConnectGithubBranchQuerySchema, ConnectGithubLinkBodySchema,
15
+ ConnectGithubPublishBodySchema, ConnectGithubRepoQuerySchema, ConnectProductionDomainBodySchema,
16
+ ConnectProductionRedirectsBodySchema, ConnectIamGroupParamsSchema, ConnectIamMemberParamsSchema,
17
+ ConnectIamOrganizationParamsSchema, ConnectIamUserParamsSchema, ConnectActivityQuerySchema, ConnectFeedQuerySchema,
12
18
  } from './schemas.js'
13
- import type { ConnectProjectBranding, ConnectProjectBrandingSave } from './branding/types.js'
19
+ import type {
20
+ ConnectAccessTokenList, ConnectAccessTokenParams, ConnectAccessTokenRevoked, ConnectBrandingBackfill, ConnectIntentPickup,
21
+ ConnectIntentPickupBody, ConnectOrganizationBranding, ConnectOrganizationBrandingSave, ConnectPrivacyChoices,
22
+ ConnectPrivacyWithdrawBody,
23
+ } from './account/types.js'
24
+ import type { ConnectBrandingCreditBody, ConnectProjectBranding, ConnectProjectBrandingSave } from './branding/types.js'
25
+ import type { ConnectCallCollectParams, ConnectCallCollectQuery, ConnectCallResult } from './call/types.js'
26
+ import type { ConnectConfigSaveBody, ConnectProjectConfig, ConnectScopeQuery } from './config/types.js'
14
27
  import type {
15
28
  ConnectConvertCreateBody, ConnectConvertProceedBody, ConnectConvertStartBody, ConversionStatusView, ConvertCheck
16
29
  } from './conversion/types.js'
17
- import type { ConnectEntrypointOptions } from './entrypoints/types.js'
30
+ import type { ConnectEntrypointOptions, ConnectGateRef } from './entrypoints/types.js'
31
+ import type {
32
+ ConnectFileContent, ConnectFileMetaQuery, ConnectFileQuery, ConnectFileSaveBody, ConnectFileWritten,
33
+ } from './files/types.js'
34
+ import type {
35
+ ConnectGitCommit, ConnectGitCommitBody, ConnectGitCommitResult, ConnectGitRevertBody, ConnectGitRevertResult,
36
+ ConnectGitState, ConnectGitStatus, ConnectGitSyncResult, ConnectGithubAuthorize, ConnectGithubBranchList,
37
+ ConnectGithubBranchQuery, ConnectGithubConnection, ConnectGithubDisconnected, ConnectGithubLinkBody, ConnectGithubLinked,
38
+ ConnectGithubPublishBody, ConnectGithubRepoList, ConnectGithubRepoQuery,
39
+ } from './git/types.js'
18
40
  import type { ConnectKitApplyBody, ConnectKitApplyResult, ConnectKitDescribe } from './kit/types.js'
41
+ import type { ConnectActivityQuery, ConnectFeedPage, ConnectFeedQuery, ConnectFileChanges } from './feed/types.js'
19
42
  import type { ConnectInquiryAnswerBody, ConnectOpResult } from './ops/types.js'
20
43
  import type { ConnectPipelineParams, ConnectPipelineResumeBody, ConnectPipelineState } from './pipeline/types.js'
21
44
  import type {
22
- ConnectAttachBody, ConnectConfirmBody, ConnectCreateBody, ConnectModifyBody, ConnectProjectStatus,
23
- ConnectStoryStatus
45
+ ConnectAgentLock, ConnectAttachBody, ConnectConfirmBody, ConnectCreateBody, ConnectModifyBody, ConnectRenameBody,
46
+ ConnectProjectStatus, ConnectProjectSummary, ConnectStoryStatus
24
47
  } from './project/types.js'
48
+ import type {
49
+ ConnectProductionAuth, ConnectProductionDomain, ConnectProductionDomainBody, ConnectProductionRedirects,
50
+ ConnectProductionRedirectsBody, ConnectProductionStatus,
51
+ } from './production/types.js'
25
52
  import type { ConnectPullQuery, ConnectSessionOpen, ConnectSessionParams } from './session/types.js'
53
+ import type { ConnectLlmBody, ConnectProfileSettingsView, ConnectProjectLlmBody, ConnectProjectSettings } from './settings/types.js'
54
+ import type { ConnectSlotView } from './slot/types.js'
55
+ import type {
56
+ ConnectIamGroupParams, ConnectIamMemberParams, ConnectIamOrganizationParams, ConnectIamUserParams,
57
+ } from './iam/types.js'
58
+ import {
59
+ IamAssignGrantSchema, IamDefinitionUpdateSchema, IamGrantsQuerySchema, IamGroupCreateSchema, IamGroupEditSchema,
60
+ IamGroupMembersChangeSchema, IamOrganizationEditSchema, IamProjectMemberInviteSchema, IamProjectMemberUpdateSchema,
61
+ IamProjectUserInviteSchema, IamProjectUserUpdateSchema, IamRevokeGrantSchema, IamScopedSchema,
62
+ } from '../iam-console/schemas.js'
63
+ import type {
64
+ IamAssignGrant, IamDefinitionUpdate, IamGrant, IamGrantsQuery, IamGrantsResponse, IamGroup, IamGroupCreate,
65
+ IamGroupEdit, IamGroupMembersChange, IamGroupsResponse, IamMember, IamMembersResponse, IamOrganization,
66
+ IamOrganizationEdit, IamOrganizationsResponse, IamPermissionDefinition, IamPermissionsResponse,
67
+ IamProjectMemberInvite, IamProjectMemberUpdate, IamProjectUserInvite, IamProjectUserUpdate, IamRevokeGrant,
68
+ IamScoped, IamUser, IamUsersResponse,
69
+ } from '../iam-console/types.js'
26
70
 
27
71
  /**
28
72
  * Declare the connector's HTTP surface — exactly the routes a connector calls: the session (long
29
- * poll, no socket), the operation relay, projects, story status, generated files, conversion,
30
- * inquiry answers and pipeline state.
73
+ * poll, no socket), the operation relay, projects (their lifecycle and agent lock included), story
74
+ * status, generated files (read, written, deleted, metadata listed), the preview workload's controls,
75
+ * the organization's workloads, the project's git repository and GitHub connection, its production
76
+ * workload (published, restarted, stopped, its custom domain and standalone sign-in), its generated
77
+ * app's sign-in (end users, permission definitions, grants, organizations and groups), conversion,
78
+ * inquiry answers, pipeline state, the collection of a
79
+ * delegated write answered early, the three feeds a connector reads instead of the browser's
80
+ * sockets (a project's activity, the organization's notices, the preview's file changes — cursor
81
+ * polling, held at most `CONNECT_FEED_WAIT_MAX_SEC`), and the organization's own records under the
82
+ * account base — its branding defaults, the person's inference preference, access tokens (listed and
83
+ * revoked, never minted), marketing consents (read and withdrawn, never granted), the intent pickup
84
+ * and the read-only list of every end user of the organization's apps.
31
85
  *
32
86
  * One immutable tree, mounted directly by the platform's entrypoint tree. Handlers are bound to these protocols
33
87
  * server-side and onto client entrypoints in the SDK — the same declarations both times, which is
@@ -38,15 +92,120 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
38
92
  const ownership = opts.gate != null
39
93
  ? { guards: opts.guard, gate: opts.gate }
40
94
  : { guards: opts.guard }
41
- const paid = opts.localLlm != null
42
- ? { gate: opts.localLlm }
43
- : undefined
95
+ const accountOwnership = opts.accountGate != null
96
+ ? { guards: opts.guard, gate: opts.accountGate }
97
+ : { guards: opts.guard }
98
+ // The payment gate of a route that spends `kind`, as the deployment names it — or none.
99
+ const paidGate = (kind: ConnectPaidGate): { gate: ConnectGateRef } | undefined => {
100
+ const gate = opts.paid?.[kind] ?? (kind === ConnectPaidGate.LocalLlm ? opts.localLlm : undefined)
101
+
102
+ return gate != null ? { gate } : undefined
103
+ }
104
+ const paid = paidGate(ConnectPaidGate.LocalLlm)
44
105
 
45
106
  const base = openProtocol(route(connect.base, prefix), ownership)
107
+ // The IAM routes' shared request parts: the project, then the subject, organization or group.
108
+ const organization = '/project/:id/iam/organizations/:entitySlug'
109
+ const group = `${organization}/groups/:group`
110
+ const projectParams = { params: typed<{ id: string }>(ConnectProjectIdSchema) }
111
+ const userParams = { params: typed<ConnectIamUserParams>(ConnectIamUserParamsSchema) }
112
+ const organizationParams = { params: typed<ConnectIamOrganizationParams>(ConnectIamOrganizationParamsSchema) }
113
+ const memberParams = { params: typed<ConnectIamMemberParams>(ConnectIamMemberParamsSchema) }
114
+ const groupParams = { params: typed<ConnectIamGroupParams>(ConnectIamGroupParamsSchema) }
115
+ const scopeQuery = { query: typed<ConnectScopeQuery>(ConnectScopeQuerySchema) }
116
+ const scopedBody = { body: typed<IamScoped>(IamScopedSchema) }
117
+ // A root of its own: a child of `base` would resolve the project gate and the account gate under
118
+ // one gate service, and `getGates()` keeps one gate per service.
119
+ const accountBase = openProtocol(route(connect.account.base, `${prefix}/account`), accountOwnership)
46
120
 
47
121
  return {
48
122
  base,
49
123
 
124
+ // --- the organization's own records ----------------------------------------------------
125
+ account: {
126
+ base: accountBase,
127
+ // The organization's branding defaults: read, a patch saved (its name and copyright judged by
128
+ // the content gate), and copied into the projects whose rows are still blank.
129
+ branding: {
130
+ get: protocol(
131
+ route(connect.account.branding.get, '/branding', { parent: accountBase, method: RouteMethod.GET }),
132
+ contract(typed<ConnectOrganizationBranding>()),
133
+ ),
134
+ save: protocol(
135
+ route(connect.account.branding.save, '/branding', { parent: accountBase, method: RouteMethod.POST }),
136
+ contract.request({
137
+ body: typed<ConnectOrganizationBrandingSave>(ConnectOrganizationBrandingSaveSchema),
138
+ }, typed<ConnectOrganizationBranding>()),
139
+ ),
140
+ backfill: protocol(
141
+ route(connect.account.branding.backfill, '/branding/backfill', { parent: accountBase, method: RouteMethod.POST }),
142
+ contract(typed<ConnectBrandingBackfill>()),
143
+ ),
144
+ },
145
+ // The person's inference preference: read free, set under the local-LLM payment gate — its
146
+ // browser twin's — since the one value a write can set that `cloud` is not is the paid one.
147
+ llm: {
148
+ get: protocol(
149
+ route(connect.account.llm.get, '/llm', { parent: accountBase, method: RouteMethod.GET }),
150
+ contract(typed<ConnectProfileSettingsView>()),
151
+ ),
152
+ set: protocol(
153
+ route(connect.account.llm.set, '/llm', { parent: accountBase, method: RouteMethod.POST }),
154
+ contract.request({ body: typed<ConnectLlmBody>(ConnectLlmBodySchema) }, typed<ConnectProfileSettingsView>()),
155
+ paid,
156
+ ),
157
+ },
158
+ // The person's own access tokens: listed and revoked — never minted here. Not under `/tokens`:
159
+ // that mount is the browser's token surface, whose create a connector must never reach.
160
+ tokens: {
161
+ list: protocol(
162
+ route(connect.account.tokens.list, '/access-tokens', { parent: accountBase, method: RouteMethod.GET }),
163
+ contract(typed<ConnectAccessTokenList>()),
164
+ ),
165
+ revoke: protocol(
166
+ route(connect.account.tokens.revoke, '/access-tokens/:id/revoke', { parent: accountBase, method: RouteMethod.POST }),
167
+ contract.request({
168
+ params: typed<ConnectAccessTokenParams>(ConnectAccessTokenParamsSchema),
169
+ }, typed<ConnectAccessTokenRevoked>()),
170
+ ),
171
+ },
172
+ // The person's marketing consents: read, and withdrawn — the body can say nothing but "no".
173
+ privacy: {
174
+ status: protocol(
175
+ route(connect.account.privacy.status, '/privacy', { parent: accountBase, method: RouteMethod.GET }),
176
+ contract(typed<ConnectPrivacyChoices>()),
177
+ ),
178
+ withdraw: protocol(
179
+ route(connect.account.privacy.withdraw, '/privacy/withdraw', { parent: accountBase, method: RouteMethod.POST }),
180
+ contract.request({
181
+ body: typed<ConnectPrivacyWithdrawBody>(ConnectPrivacyWithdrawBodySchema),
182
+ }, typed<ConnectPrivacyChoices>()),
183
+ ),
184
+ },
185
+ // A stashed prompt collected once by its reference — the guest pickup's record and throttle,
186
+ // for a caller who is already signed in.
187
+ intent: {
188
+ pickup: protocol(
189
+ route(connect.account.intent.pickup, '/intent/pickup', { parent: accountBase, method: RouteMethod.POST }),
190
+ contract.request({
191
+ body: typed<ConnectIntentPickupBody>(ConnectIntentPickupBodySchema),
192
+ }, typed<ConnectIntentPickup>()),
193
+ ),
194
+ },
195
+ // Every end user of every app the organization owns — read-only; a person is managed per project.
196
+ iam: {
197
+ users: protocol(
198
+ route(connect.account.iam.users, '/iam/users', { parent: accountBase, method: RouteMethod.GET }),
199
+ contract(typed<IamUsersResponse>()),
200
+ ),
201
+ },
202
+ // The organization's notices by cursor — the browser's notifications socket, polled.
203
+ notifications: protocol(
204
+ route(connect.account.notifications, '/notifications', { parent: accountBase, method: RouteMethod.GET }),
205
+ contract.request({ query: typed<ConnectFeedQuery>(ConnectFeedQuerySchema) }, typed<ConnectFeedPage>()),
206
+ ),
207
+ },
208
+
50
209
  // --- session ---------------------------------------------------------------------------
51
210
  session: {
52
211
  open: protocol(
@@ -124,6 +283,52 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
124
283
  }),
125
284
  contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema), body: typed<ConnectModifyBody>(ConnectModifyBodySchema) }, typed<ConnectProjectStatus>())
126
285
  ),
286
+ rename: protocol(
287
+ route(connect.project.rename, '/project/:id/rename', {
288
+ parent: base, method: RouteMethod.POST
289
+ }),
290
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema), body: typed<ConnectRenameBody>(ConnectRenameBodySchema) }, typed<ConnectProjectStatus>())
291
+ ),
292
+ // Irreversible: the project, its workloads, stories, documents and connector sessions go.
293
+ destroy: protocol(
294
+ route(connect.project.destroy, '/project/:id', {
295
+ parent: base, method: RouteMethod.DELETE
296
+ }),
297
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectProjectSummary>())
298
+ ),
299
+ // The web's force-unlock: under the owned base, no payment gate — nothing is spent.
300
+ unlock: protocol(
301
+ route(connect.project.unlock, '/project/:id/unlock', {
302
+ parent: base, method: RouteMethod.POST
303
+ }),
304
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectAgentLock>())
305
+ ),
306
+ // The project's agent activity by cursor — the browser's project socket, polled; the ownership gate alone.
307
+ activity: protocol(
308
+ route(connect.project.activity, '/project/:id/activity', {
309
+ parent: base, method: RouteMethod.GET
310
+ }),
311
+ contract.request({
312
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
313
+ query: typed<ConnectActivityQuery>(ConnectActivityQuerySchema),
314
+ }, typed<ConnectFeedPage>())
315
+ ),
316
+ // The project's inference override: read free, set under the local-LLM payment gate — its
317
+ // browser twin's; `null` unsets the override.
318
+ llm: {
319
+ get: protocol(
320
+ route(connect.project.llm.get, '/project/:id/llm', { parent: base, method: RouteMethod.GET }),
321
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectProjectSettings>()),
322
+ ),
323
+ set: protocol(
324
+ route(connect.project.llm.set, '/project/:id/llm', { parent: base, method: RouteMethod.POST }),
325
+ contract.request({
326
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
327
+ body: typed<ConnectProjectLlmBody>(ConnectProjectLlmBodySchema),
328
+ }, typed<ConnectProjectSettings>()),
329
+ paid,
330
+ ),
331
+ },
127
332
  // Planning kits: one path, read and applied under the owned base like every project route.
128
333
  kit: {
129
334
  describe: protocol(
@@ -142,8 +347,9 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
142
347
  }, typed<ConnectKitApplyResult>()),
143
348
  ),
144
349
  },
145
- // Under `base` like every sibling — the guard and the ownership gate — and never under the
146
- // paid gate: saving branding is free, and the paid credit switch is not reachable from here.
350
+ // Under `base` like every sibling — the guard and the ownership gate. The text settings are
351
+ // free; the credit switch is the one route here that carries a payment gate (white label), its
352
+ // browser twin's. `?scope=production` addresses the production workload's own rows.
147
353
  branding: {
148
354
  get: protocol(
149
355
  route(connect.project.branding.get, '/project/:id/branding', {
@@ -151,6 +357,7 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
151
357
  }),
152
358
  contract.request({
153
359
  params: typed<{ id: string }>(ConnectProjectIdSchema),
360
+ query: typed<ConnectScopeQuery>(ConnectScopeQuerySchema),
154
361
  }, typed<ConnectProjectBranding>()),
155
362
  ),
156
363
  save: protocol(
@@ -159,10 +366,57 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
159
366
  }),
160
367
  contract.request({
161
368
  params: typed<{ id: string }>(ConnectProjectIdSchema),
369
+ query: typed<ConnectScopeQuery>(ConnectScopeQuerySchema),
162
370
  body: typed<ConnectProjectBrandingSave>(ConnectProjectBrandingSaveSchema),
163
371
  }, typed<ConnectProjectBranding>()),
164
372
  ),
373
+ credit: protocol(
374
+ route(connect.project.branding.credit, '/project/:id/branding/credit', {
375
+ parent: base, method: RouteMethod.POST,
376
+ }),
377
+ contract.request({
378
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
379
+ query: typed<ConnectScopeQuery>(ConnectScopeQuerySchema),
380
+ body: typed<ConnectBrandingCreditBody>(ConnectBrandingCreditBodySchema),
381
+ }, typed<ConnectProjectBranding>()),
382
+ paidGate(ConnectPaidGate.Whitelabel),
383
+ ),
384
+ copyDefaults: protocol(
385
+ route(connect.project.branding.copyDefaults, '/project/:id/branding/copy-defaults', {
386
+ parent: base, method: RouteMethod.POST,
387
+ }),
388
+ contract.request({
389
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
390
+ query: typed<ConnectScopeQuery>(ConnectScopeQuerySchema),
391
+ }, typed<ConnectProjectBranding>()),
392
+ ),
393
+ },
165
394
  },
395
+
396
+ // --- configuration -----------------------------------------------------------------------
397
+ // The variables the generated application reads — a backend value is never answered. Nothing is
398
+ // spent, so the owned base alone; a save of the preview's set reconfigures (and rebuilds) it.
399
+ config: {
400
+ get: protocol(
401
+ route(connect.config.get, '/project/:id/config', { parent: base, method: RouteMethod.GET }),
402
+ contract.request({
403
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
404
+ query: typed<ConnectScopeQuery>(ConnectScopeQuerySchema),
405
+ }, typed<ConnectProjectConfig>()),
406
+ ),
407
+ save: protocol(
408
+ route(connect.config.save, '/project/:id/config', { parent: base, method: RouteMethod.POST }),
409
+ contract.request({
410
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
411
+ query: typed<ConnectScopeQuery>(ConnectScopeQuerySchema),
412
+ body: typed<ConnectConfigSaveBody>(ConnectConfigSaveBodySchema),
413
+ }, typed<ConnectProjectConfig>()),
414
+ ),
415
+ // The platform's own agent reads the generated sources again for the variables they declare.
416
+ recollect: protocol(
417
+ route(connect.config.recollect, '/project/:id/config/recollect', { parent: base, method: RouteMethod.POST }),
418
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectProjectConfig>()),
419
+ ),
166
420
  },
167
421
 
168
422
  story: {
@@ -178,11 +432,217 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
178
432
  },
179
433
 
180
434
  // --- generated files and conversion ----------------------------------------------------
435
+ // One file at one path — read, written whole, deleted — under the owned base and no payment
436
+ // gate, exactly like the web editor's routes. A write and a delete rebuild the preview.
181
437
  files: {
182
438
  list: protocol(
183
439
  route(connect.files.list, '/project/:id/files', { parent: base, method: RouteMethod.GET }),
184
440
  contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<string[]>()),
185
441
  ),
442
+ get: protocol(
443
+ route(connect.files.get, '/project/:id/files/content', { parent: base, method: RouteMethod.GET }),
444
+ contract.request({
445
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
446
+ query: typed<ConnectFileQuery>(ConnectFileQuerySchema),
447
+ }, typed<ConnectFileContent>()),
448
+ ),
449
+ save: protocol(
450
+ route(connect.files.save, '/project/:id/files/content', { parent: base, method: RouteMethod.POST }),
451
+ contract.request({
452
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
453
+ body: typed<ConnectFileSaveBody>(ConnectFileSaveBodySchema),
454
+ }, typed<ConnectFileWritten>()),
455
+ ),
456
+ remove: protocol(
457
+ route(connect.files.remove, '/project/:id/files/content', { parent: base, method: RouteMethod.DELETE }),
458
+ contract.request({
459
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
460
+ query: typed<ConnectFileQuery>(ConnectFileQuerySchema),
461
+ }, typed<ConnectFileWritten>()),
462
+ ),
463
+ meta: protocol(
464
+ route(connect.files.meta, '/project/:id/files/meta', { parent: base, method: RouteMethod.GET }),
465
+ contract.request({
466
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
467
+ query: typed<ConnectFileMetaQuery>(ConnectFileMetaQuerySchema),
468
+ }, typed<string[]>()),
469
+ ),
470
+ // The preview tree's changes by cursor — the web editor's watch socket, polled.
471
+ changes: protocol(
472
+ route(connect.files.changes, '/project/:id/files/changes', { parent: base, method: RouteMethod.GET }),
473
+ contract.request({
474
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
475
+ query: typed<ConnectFeedQuery>(ConnectFeedQuerySchema),
476
+ }, typed<ConnectFileChanges>()),
477
+ ),
478
+ },
479
+
480
+ // --- the preview workload and the organization's workloads -----------------------------
481
+ // The web's sandbox controls: each answers the preview workload as it then stands. Nothing is
482
+ // spent, so the owned base alone.
483
+ sandbox: {
484
+ run: protocol(
485
+ route(connect.sandbox.run, '/project/:id/sandbox/run', { parent: base, method: RouteMethod.POST }),
486
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectSlotView>()),
487
+ ),
488
+ restart: protocol(
489
+ route(connect.sandbox.restart, '/project/:id/sandbox/restart', { parent: base, method: RouteMethod.POST }),
490
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectSlotView>()),
491
+ ),
492
+ stop: protocol(
493
+ route(connect.sandbox.stop, '/project/:id/sandbox/stop', { parent: base, method: RouteMethod.POST }),
494
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectSlotView>()),
495
+ ),
496
+ rebuild: protocol(
497
+ route(connect.sandbox.rebuild, '/project/:id/sandbox/rebuild', { parent: base, method: RouteMethod.POST }),
498
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectSlotView>()),
499
+ ),
500
+ },
501
+
502
+ slot: {
503
+ list: protocol(
504
+ route(connect.slot.list, '/slot', { parent: base, method: RouteMethod.GET }),
505
+ contract(typed<ConnectSlotView[]>()),
506
+ ),
507
+ },
508
+
509
+ // --- git and GitHub ---------------------------------------------------------------------
510
+ // The web Git dialog's own calls, under the owned base and no payment gate — nothing is spent.
511
+ // GitHub is AUTHORIZED here (the answer is the address the person opens) and never completed:
512
+ // GitHub returns to the platform's web application, which completes it. No answer carries the
513
+ // GitHub token.
514
+ git: {
515
+ status: protocol(
516
+ route(connect.git.status, '/project/:id/git', { parent: base, method: RouteMethod.GET }),
517
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectGitState>()),
518
+ ),
519
+ log: protocol(
520
+ route(connect.git.log, '/project/:id/git/log', { parent: base, method: RouteMethod.GET }),
521
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectGitCommit[]>()),
522
+ ),
523
+ commit: protocol(
524
+ route(connect.git.commit, '/project/:id/git/commit', { parent: base, method: RouteMethod.POST }),
525
+ contract.request({
526
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
527
+ body: typed<ConnectGitCommitBody>(ConnectGitCommitBodySchema),
528
+ }, typed<ConnectGitCommitResult>()),
529
+ ),
530
+ discard: protocol(
531
+ route(connect.git.discard, '/project/:id/git/discard', { parent: base, method: RouteMethod.POST }),
532
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectGitStatus>()),
533
+ ),
534
+ revert: protocol(
535
+ route(connect.git.revert, '/project/:id/git/revert', { parent: base, method: RouteMethod.POST }),
536
+ contract.request({
537
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
538
+ body: typed<ConnectGitRevertBody>(ConnectGitRevertBodySchema),
539
+ }, typed<ConnectGitRevertResult>()),
540
+ ),
541
+ },
542
+
543
+ github: {
544
+ authorize: protocol(
545
+ route(connect.github.authorize, '/project/:id/github/authorize', { parent: base, method: RouteMethod.POST }),
546
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectGithubAuthorize>()),
547
+ ),
548
+ publish: protocol(
549
+ route(connect.github.publish, '/project/:id/github/publish', { parent: base, method: RouteMethod.POST }),
550
+ contract.request({
551
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
552
+ body: typed<ConnectGithubPublishBody>(ConnectGithubPublishBodySchema),
553
+ }, typed<ConnectGithubConnection>()),
554
+ ),
555
+ push: protocol(
556
+ route(connect.github.push, '/project/:id/github/push', { parent: base, method: RouteMethod.POST }),
557
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectGitSyncResult>()),
558
+ ),
559
+ pull: protocol(
560
+ route(connect.github.pull, '/project/:id/github/pull', { parent: base, method: RouteMethod.POST }),
561
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectGitSyncResult>()),
562
+ ),
563
+ disconnect: protocol(
564
+ route(connect.github.disconnect, '/project/:id/github/disconnect', { parent: base, method: RouteMethod.POST }),
565
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectGithubDisconnected>()),
566
+ ),
567
+ repos: protocol(
568
+ route(connect.github.repos, '/project/:id/github/repos', { parent: base, method: RouteMethod.GET }),
569
+ contract.request({
570
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
571
+ query: typed<ConnectGithubRepoQuery>(ConnectGithubRepoQuerySchema),
572
+ }, typed<ConnectGithubRepoList>()),
573
+ ),
574
+ branches: protocol(
575
+ route(connect.github.branches, '/project/:id/github/branches', { parent: base, method: RouteMethod.GET }),
576
+ contract.request({
577
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
578
+ query: typed<ConnectGithubBranchQuery>(ConnectGithubBranchQuerySchema),
579
+ }, typed<ConnectGithubBranchList>()),
580
+ ),
581
+ link: protocol(
582
+ route(connect.github.link, '/project/:id/github/link', { parent: base, method: RouteMethod.POST }),
583
+ contract.request({
584
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
585
+ body: typed<ConnectGithubLinkBody>(ConnectGithubLinkBodySchema),
586
+ }, typed<ConnectGithubLinked>()),
587
+ ),
588
+ },
589
+
590
+ // --- the production workload ----------------------------------------------------------
591
+ // The web Publish dialog's own calls. Each carries exactly its browser twin's payment gate: a
592
+ // publish holds a published-sites unit (the LIMIT gate), attaching a custom domain and the
593
+ // standalone sign-in are capabilities; the status, restart, stop, verify and detach are owned
594
+ // calls alone (a restart of a stopped site takes its unit in the handler, as the browser's does).
595
+ production: {
596
+ status: protocol(
597
+ route(connect.production.status, '/project/:id/production', { parent: base, method: RouteMethod.GET }),
598
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectProductionStatus>()),
599
+ ),
600
+ publish: protocol(
601
+ route(connect.production.publish, '/project/:id/production/publish', { parent: base, method: RouteMethod.POST }),
602
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectProductionStatus>()),
603
+ paidGate(ConnectPaidGate.PublishedSites),
604
+ ),
605
+ restart: protocol(
606
+ route(connect.production.restart, '/project/:id/production/restart', { parent: base, method: RouteMethod.POST }),
607
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectProductionStatus>()),
608
+ ),
609
+ stop: protocol(
610
+ route(connect.production.stop, '/project/:id/production/stop', { parent: base, method: RouteMethod.POST }),
611
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectProductionStatus>()),
612
+ ),
613
+ domain: {
614
+ attach: protocol(
615
+ route(connect.production.domain.attach, '/project/:id/production/domain/attach', { parent: base, method: RouteMethod.POST }),
616
+ contract.request({
617
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
618
+ body: typed<ConnectProductionDomainBody>(ConnectProductionDomainBodySchema),
619
+ }, typed<ConnectProductionDomain | null>()),
620
+ paidGate(ConnectPaidGate.CustomDomain),
621
+ ),
622
+ verify: protocol(
623
+ route(connect.production.domain.verify, '/project/:id/production/domain/verify', { parent: base, method: RouteMethod.POST }),
624
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectProductionDomain | null>()),
625
+ ),
626
+ detach: protocol(
627
+ route(connect.production.domain.detach, '/project/:id/production/domain/detach', { parent: base, method: RouteMethod.POST }),
628
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectProductionDomain | null>()),
629
+ ),
630
+ },
631
+ auth: {
632
+ get: protocol(
633
+ route(connect.production.auth.get, '/project/:id/production/auth', { parent: base, method: RouteMethod.GET }),
634
+ contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectProductionAuth>()),
635
+ paidGate(ConnectPaidGate.ProductionStandalone),
636
+ ),
637
+ redirects: protocol(
638
+ route(connect.production.auth.redirects, '/project/:id/production/auth/redirects', { parent: base, method: RouteMethod.POST }),
639
+ contract.request({
640
+ params: typed<{ id: string }>(ConnectProjectIdSchema),
641
+ body: typed<ConnectProductionRedirectsBody>(ConnectProductionRedirectsBodySchema),
642
+ }, typed<ConnectProductionRedirects>()),
643
+ paidGate(ConnectPaidGate.ProductionStandalone),
644
+ ),
645
+ },
186
646
  },
187
647
 
188
648
  convert: {
@@ -230,6 +690,110 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
230
690
  ),
231
691
  },
232
692
 
693
+ // --- the generated app's sign-in (its IAM) ----------------------------------------------
694
+ // The owner console's own calls, per project and per app (`scope`: the preview's client by
695
+ // default, or production's), under the ownership gate alone — exactly their browser twins'
696
+ // (`back.iam.*`). The bodies are the browser's own schemas; every write is a POST, a remove's
697
+ // `scope` rides in its body; a read takes it in the query. No staff-synchronization twin.
698
+ iam: {
699
+ permissions: protocol(
700
+ route(connect.iam.permissions, '/project/:id/iam/permissions', { parent: base, method: RouteMethod.GET }),
701
+ contract.request({ ...projectParams, ...scopeQuery }, typed<IamPermissionsResponse>()),
702
+ ),
703
+ defaultUpdate: protocol(
704
+ route(connect.iam.defaultUpdate, '/project/:id/iam/permissions/default', { parent: base, method: RouteMethod.POST }),
705
+ contract.request({ ...projectParams, body: typed<IamDefinitionUpdate>(IamDefinitionUpdateSchema) }, typed<IamPermissionDefinition>()),
706
+ ),
707
+ grants: {
708
+ list: protocol(
709
+ route(connect.iam.grants.list, '/project/:id/iam/grants', { parent: base, method: RouteMethod.GET }),
710
+ contract.request({ ...projectParams, query: typed<IamGrantsQuery>(IamGrantsQuerySchema) }, typed<IamGrantsResponse>()),
711
+ ),
712
+ assign: protocol(
713
+ route(connect.iam.grants.assign, '/project/:id/iam/grants', { parent: base, method: RouteMethod.POST }),
714
+ contract.request({ ...projectParams, body: typed<IamAssignGrant>(IamAssignGrantSchema) }, typed<IamGrant>()),
715
+ ),
716
+ revoke: protocol(
717
+ route(connect.iam.grants.revoke, '/project/:id/iam/grants/revoke', { parent: base, method: RouteMethod.POST }),
718
+ contract.request({ ...projectParams, body: typed<IamRevokeGrant>(IamRevokeGrantSchema) }, typed<undefined>()),
719
+ ),
720
+ },
721
+ users: {
722
+ list: protocol(
723
+ route(connect.iam.users.list, '/project/:id/iam/users', { parent: base, method: RouteMethod.GET }),
724
+ contract.request({ ...projectParams, ...scopeQuery }, typed<IamUsersResponse>()),
725
+ ),
726
+ invite: protocol(
727
+ route(connect.iam.users.invite, '/project/:id/iam/users', { parent: base, method: RouteMethod.POST }),
728
+ contract.request({ ...projectParams, body: typed<IamProjectUserInvite>(IamProjectUserInviteSchema) }, typed<IamUser>()),
729
+ ),
730
+ update: protocol(
731
+ route(connect.iam.users.update, '/project/:id/iam/users/:profileId', { parent: base, method: RouteMethod.POST }),
732
+ contract.request({ ...userParams, body: typed<IamProjectUserUpdate>(IamProjectUserUpdateSchema) }, typed<IamUser>()),
733
+ ),
734
+ remove: protocol(
735
+ route(connect.iam.users.remove, '/project/:id/iam/users/:profileId/remove', { parent: base, method: RouteMethod.POST }),
736
+ contract.request({ ...userParams, ...scopedBody }, typed<undefined>()),
737
+ ),
738
+ },
739
+ organizations: {
740
+ list: protocol(
741
+ route(connect.iam.organizations.list, '/project/:id/iam/organizations', { parent: base, method: RouteMethod.GET }),
742
+ contract.request({ ...projectParams, ...scopeQuery }, typed<IamOrganizationsResponse>()),
743
+ ),
744
+ update: protocol(
745
+ route(connect.iam.organizations.update, organization, { parent: base, method: RouteMethod.POST }),
746
+ contract.request({ ...organizationParams, body: typed<IamOrganizationEdit>(IamOrganizationEditSchema) }, typed<IamOrganization>()),
747
+ ),
748
+ members: protocol(
749
+ route(connect.iam.organizations.members, `${organization}/members`, { parent: base, method: RouteMethod.GET }),
750
+ contract.request({ ...organizationParams, ...scopeQuery }, typed<IamMembersResponse>()),
751
+ ),
752
+ addMember: protocol(
753
+ route(connect.iam.organizations.addMember, `${organization}/members`, { parent: base, method: RouteMethod.POST }),
754
+ contract.request({ ...organizationParams, body: typed<IamProjectMemberInvite>(IamProjectMemberInviteSchema) }, typed<IamMember>()),
755
+ ),
756
+ updateMember: protocol(
757
+ route(connect.iam.organizations.updateMember, `${organization}/members/:profileId`, { parent: base, method: RouteMethod.POST }),
758
+ contract.request({ ...memberParams, body: typed<IamProjectMemberUpdate>(IamProjectMemberUpdateSchema) }, typed<IamMember>()),
759
+ ),
760
+ removeMember: protocol(
761
+ route(connect.iam.organizations.removeMember, `${organization}/members/:profileId/remove`, { parent: base, method: RouteMethod.POST }),
762
+ contract.request({ ...memberParams, ...scopedBody }, typed<undefined>()),
763
+ ),
764
+ },
765
+ groups: {
766
+ list: protocol(
767
+ route(connect.iam.groups.list, `${organization}/groups`, { parent: base, method: RouteMethod.GET }),
768
+ contract.request({ ...organizationParams, ...scopeQuery }, typed<IamGroupsResponse>()),
769
+ ),
770
+ ensure: protocol(
771
+ route(connect.iam.groups.ensure, `${organization}/groups`, { parent: base, method: RouteMethod.POST }),
772
+ contract.request({ ...organizationParams, body: typed<IamGroupCreate>(IamGroupCreateSchema) }, typed<IamGroup>()),
773
+ ),
774
+ update: protocol(
775
+ route(connect.iam.groups.update, group, { parent: base, method: RouteMethod.POST }),
776
+ contract.request({ ...groupParams, body: typed<IamGroupEdit>(IamGroupEditSchema) }, typed<IamGroup>()),
777
+ ),
778
+ remove: protocol(
779
+ route(connect.iam.groups.remove, `${group}/remove`, { parent: base, method: RouteMethod.POST }),
780
+ contract.request({ ...groupParams, ...scopedBody }, typed<undefined>()),
781
+ ),
782
+ members: protocol(
783
+ route(connect.iam.groups.members, `${group}/members`, { parent: base, method: RouteMethod.GET }),
784
+ contract.request({ ...groupParams, ...scopeQuery }, typed<IamMembersResponse>()),
785
+ ),
786
+ addMembers: protocol(
787
+ route(connect.iam.groups.addMembers, `${group}/members`, { parent: base, method: RouteMethod.POST }),
788
+ contract.request({ ...groupParams, body: typed<IamGroupMembersChange>(IamGroupMembersChangeSchema) }, typed<undefined>()),
789
+ ),
790
+ removeMembers: protocol(
791
+ route(connect.iam.groups.removeMembers, `${group}/members/remove`, { parent: base, method: RouteMethod.POST }),
792
+ contract.request({ ...groupParams, body: typed<IamGroupMembersChange>(IamGroupMembersChangeSchema) }, typed<undefined>()),
793
+ ),
794
+ },
795
+ },
796
+
233
797
  // --- pipelines -------------------------------------------------------------------------
234
798
  pipeline: {
235
799
  state: protocol(
@@ -245,5 +809,18 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
245
809
  contract.request({ params: typed<ConnectPipelineParams>(ConnectPipelineParamsSchema), body: typed<ConnectPipelineResumeBody>(ConnectPipelineResumeBodySchema) }, typed<ConnectPipelineState>())
246
810
  ),
247
811
  },
812
+
813
+ // --- request now, collect later --------------------------------------------------------
814
+ // A delegated write answered `{ pending }` is collected here by its `x-viable-call` id: a long
815
+ // poll under the base guard and never the paid gate — collecting is not a new use of anything.
816
+ call: {
817
+ collect: protocol(
818
+ route(connect.call.collect, '/call/:callId', { parent: base, method: RouteMethod.GET }),
819
+ contract.request({
820
+ params: typed<ConnectCallCollectParams>(ConnectCallCollectParamsSchema),
821
+ query: typed<ConnectCallCollectQuery>(ConnectCallCollectQuerySchema),
822
+ }, typed<ConnectCallResult>()),
823
+ ),
824
+ },
248
825
  }
249
826
  }