@fabrika/provider-zerops 0.4.2 → 0.4.4

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 (49) hide show
  1. package/package.json +8 -7
  2. package/src/api/client.ts +11 -14
  3. package/src/api/polling.ts +42 -0
  4. package/src/api/readers.ts +9 -2
  5. package/src/api/service-env-limit.ts +29 -0
  6. package/src/api/service-env-response.ts +34 -2
  7. package/src/api/types.ts +39 -9
  8. package/src/api.ts +10 -1
  9. package/src/control.ts +23 -8
  10. package/src/execution.ts +44 -5
  11. package/src/index.ts +5 -0
  12. package/src/installation/admin.ts +4 -2
  13. package/src/installation/checker-release.ts +44 -0
  14. package/src/installation/deploy.ts +95 -37
  15. package/src/installation/generated/platform-console-schema.ts +18 -2
  16. package/src/installation/generated/platform-proxy-manifest.ts +20 -0
  17. package/src/installation/guide.md +169 -52
  18. package/src/installation/index.ts +22 -15
  19. package/src/installation/init-options.ts +2 -2
  20. package/src/installation/init.ts +8 -2
  21. package/src/installation/install-collaborators.ts +4 -0
  22. package/src/installation/install-environment.ts +52 -30
  23. package/src/installation/install-options.ts +20 -19
  24. package/src/installation/install-project.ts +23 -15
  25. package/src/installation/install-provisioning.ts +12 -12
  26. package/src/installation/install-secrets.ts +89 -0
  27. package/src/installation/install-token.ts +37 -0
  28. package/src/installation/install.ts +34 -21
  29. package/src/installation/machine-key.ts +3 -2
  30. package/src/installation/platform-archive.ts +87 -0
  31. package/src/installation/platform-upload.ts +2 -2
  32. package/src/installation/secrets.ts +3 -4
  33. package/src/installation/sidecar.ts +38 -2
  34. package/src/installation/templates/README.md +1 -1
  35. package/src/installation/templates/platform.yml +23 -2
  36. package/src/installation/topology/setups.ts +15 -27
  37. package/src/installation/topology/topology.ts +105 -104
  38. package/src/installation/upgrade.ts +24 -7
  39. package/src/managed-env.ts +11 -0
  40. package/src/manifest.ts +25 -3
  41. package/src/namespace/codec.ts +6 -1
  42. package/src/namespace/errors.ts +3 -1
  43. package/src/namespace/provisioning.ts +18 -5
  44. package/src/namespace/proxy-attempt.ts +56 -14
  45. package/src/namespace/update.ts +50 -8
  46. package/src/provider.ts +57 -13
  47. package/src/proxy-manifest-carrier.ts +32 -0
  48. package/src/source/port.ts +9 -1
  49. package/src/installation/manifest.ts +0 -95
@@ -19,9 +19,9 @@
19
19
  //
20
20
  // ── What this command writes, and what it deliberately does not ───────────────────────────────────
21
21
  //
22
- // It writes the composed proxy manifest, the exact namespace planning source default, and the
23
- // per-installation configuration derived from the resolved public hosts (each service's own origin,
24
- // the origins it must know about, and the installation's environment name). **It writes no credential at all** — every
22
+ // It composes the proxy manifest and ships it in the proxy's build archive. It writes the exact namespace
23
+ // planning source default and the per-installation configuration derived from the resolved public hosts
24
+ // (each service's own origin, the origins it must know about, and the installation's environment name). **It writes no credential at all** — every
25
25
  // secret an installation holds is `platform init`'s to place, and a deploy that cannot write one cannot
26
26
  // leak one. The two credentials this command HOLDS are the Zerops access token and the IAM admin key,
27
27
  // both from the environment, neither ever logged.
@@ -35,10 +35,14 @@
35
35
  // readiness gate.
36
36
 
37
37
  import { FABRIKA_SERVICE_TABLE_DIGEST, readEnvironmentName } from '@fabrika/auth-core'
38
+ import { IAM_APP_ID, IAM_APP_SCHEMA } from '@fabrika/iam-contract'
38
39
  import type { SchemaReconciler } from '@fabrika/provider-contract'
39
40
  import { encodeProxyManifestJson, FABRIKA_PROXY_MANIFEST_JSON } from '@fabrika/proxy-core/manifest'
41
+ import { assertServiceEnvValueFits } from '../api'
40
42
  import {
43
+ enableSubdomainAccessWhenHttp,
41
44
  prepareZeropsDescriptor,
45
+ readableContent,
42
46
  type Sleeper,
43
47
  ZEROPS_ACTIVE,
44
48
  ZEROPS_SERVICE_NOT_HTTP,
@@ -47,7 +51,10 @@ import {
47
51
  ZeropsApiError,
48
52
  type ZeropsNamespaceProxySource,
49
53
  type ZeropsService,
54
+ type ZeropsServiceEnv,
50
55
  } from '../index'
56
+ import { ZEROPS_PROXY_MANIFEST_PATH } from '../proxy-manifest-carrier'
57
+ import { assertCheckerPublished, type CheckerPublication } from './checker-release'
51
58
  import type { PlatformDeployInput } from './deploy-options'
52
59
  import { PLATFORM_CONSOLE_APP_SCHEMA } from './generated/platform-console-schema'
53
60
  import { PLATFORM_PROXY_MANIFEST_TEMPLATE } from './generated/platform-proxy-manifest'
@@ -61,8 +68,8 @@ import {
61
68
  validateInstallationSetups,
62
69
  } from './installation-source'
63
70
  import type { DeployLog } from './log'
64
- import { mergePlatformProxyManifest, readLiveProxyManifest } from './manifest'
65
71
  import { type IamOAuthConfig, platformIamOAuthConfig } from './mcp'
72
+ import { archiveWithDataFile } from './platform-archive'
66
73
  import type { PreparedPlatformSource } from './platform-source'
67
74
  import { type PlatformUploadFetch, uploadPlatformArchive } from './platform-upload'
68
75
  import { platformProxyAppFor, type PlatformProxyComposition, platformServiceTableDigest, resolvePlatformProxyManifest } from './proxy-manifest'
@@ -139,6 +146,7 @@ export interface PlatformServiceDeployCollaborators {
139
146
  /** Everything `deployPlatform` reaches the outside world through. */
140
147
  export interface PlatformDeployCollaborators extends PlatformServiceDeployCollaborators {
141
148
  readonly prepareSource: PlatformSourcePreparer
149
+ readonly checkerPublished: CheckerPublication
142
150
  readonly reconcileSchema: SchemaReconciler
143
151
  }
144
152
 
@@ -146,6 +154,16 @@ export interface PlatformDeployCollaborators extends PlatformServiceDeployCollab
146
154
  export interface PlatformServiceDeployInput {
147
155
  readonly source: PreparedPlatformSource
148
156
  readonly dryRun: boolean
157
+ /** The proxy's manifest, which its archive carries as the one data file (ADR-0045); the proxy is refused without it. */
158
+ readonly proxyManifest?: string
159
+ }
160
+
161
+ /** The archive one service builds from: the snapshot, and for the proxy the snapshot plus its manifest. */
162
+ const serviceArchive = async (service: ResolvedService, input: PlatformServiceDeployInput): Promise<Blob> => {
163
+ const snapshot = Bun.file(input.source.archivePath)
164
+ if (service.hostname !== PLATFORM_PROXY_SERVICE) return snapshot
165
+ if (input.proxyManifest === undefined) throw new Error('the proxy is built only with the manifest its archive carries')
166
+ return new Blob([archiveWithDataFile(await snapshot.bytes(), { path: ZEROPS_PROXY_MANIFEST_PATH, contents: input.proxyManifest })])
149
167
  }
150
168
 
151
169
  /** One resolved platform service. */
@@ -237,13 +255,16 @@ export const serviceOf = (services: ReadonlyMap<PlatformDeployService, ResolvedS
237
255
  return service
238
256
  }
239
257
 
240
- /** Read one service's environment as a key → value map. Values are never logged. */
258
+ /** One service's environment by key. A sensitive record carries no value. Values are never logged. */
259
+ export type LiveServiceEnv = ReadonlyMap<string, ZeropsServiceEnv>
260
+
261
+ /** Read one service's environment by key. */
241
262
  export const readServiceEnv = async (
242
263
  serviceId: string,
243
264
  { api, signal }: Pick<PlatformServiceDeployCollaborators, 'api' | 'signal'>,
244
- ): Promise<Map<string, string>> => {
265
+ ): Promise<LiveServiceEnv> => {
245
266
  const variables = await api.listServiceEnv({ serviceId, signal })
246
- return new Map(variables.map((variable) => [variable.key, variable.content]))
267
+ return new Map(variables.map((variable) => [variable.key, variable]))
247
268
  }
248
269
 
249
270
  /**
@@ -254,11 +275,11 @@ export const readServiceEnv = async (
254
275
  * `custom-domain` installation, where the domains are bound to the project balancer out of band and
255
276
  * publishing a subdomain would add a public address nobody asked for.
256
277
  */
257
- const resolvePlacement = (input: PlatformDeployInput, proxyEnv: ReadonlyMap<string, string>): PlatformPlacement => {
278
+ const resolvePlacement = (input: PlatformDeployInput, proxyEnv: LiveServiceEnv): PlatformPlacement => {
258
279
  if (input.hosts !== undefined) {
259
280
  return { hosts: input.hosts, scheme: input.scheme, zeropsSubdomain: false }
260
281
  }
261
- const subdomains = proxyEnv.get(ZEROPS_SUBDOMAIN_VARIABLE)
282
+ const subdomains = readableContent(proxyEnv.get(ZEROPS_SUBDOMAIN_VARIABLE))
262
283
  if (subdomains === undefined || subdomains.trim() === '') {
263
284
  throw new Error(
264
285
  'the proxy service publishes no Zerops subdomain — deploy it once so it has HTTP ports, or name every host '
@@ -281,7 +302,6 @@ const IAM_OAUTH_CONFIG = 'FABRIKA_IAM_OAUTH_CONFIG'
281
302
  const desiredServiceEnv = (
282
303
  environment: string,
283
304
  placement: PlatformPlacement,
284
- manifestJson: string,
285
305
  proxySource: ZeropsNamespaceProxySource,
286
306
  oauthConfig: IamOAuthConfig | undefined,
287
307
  controlTableDigest: string,
@@ -314,7 +334,6 @@ const desiredServiceEnv = (
314
334
  // Compared byte-for-byte against a token's `iss`, so this is the PUBLIC issuer and not the
315
335
  // private `iam:3000` the manifest dials — `readProxyEnv` canonicalizes it to an origin.
316
336
  ['FABRIKA_IAM_ISSUER', iamOrigin],
317
- [FABRIKA_PROXY_MANIFEST_JSON, manifestJson],
318
337
  ['FABRIKA_PROXY_SESSION_OWNER_JSON', JSON.stringify({ issuer: iamOrigin, upstream: PRIVATE_IAM_UPSTREAM })],
319
338
  ]),
320
339
  ],
@@ -338,29 +357,56 @@ const desiredServiceEnv = (
338
357
  ])
339
358
  }
340
359
 
341
- /** Write only what differs. Returns the KEYS written — never a value, not even in a debug line. */
360
+ /**
361
+ * Write only what differs. Returns the KEYS written — never a value, not even in a debug line.
362
+ *
363
+ * Nothing here is a credential, so everything is written readable. A record an older release wrote
364
+ * sensitive reads as different and is rewritten readable, which is what lets the next deploy compare it.
365
+ */
342
366
  const applyServiceEnv = async (
343
367
  service: ResolvedService,
344
368
  desired: ReadonlyMap<string, string>,
345
- live: ReadonlyMap<string, string>,
369
+ live: LiveServiceEnv,
346
370
  input: PlatformDeployInput,
347
371
  collaborators: PlatformDeployCollaborators,
348
372
  ): Promise<string[]> => {
349
373
  const written: string[] = []
350
374
  for (const [key, value] of desired) {
351
- if (live.get(key) === value) {
375
+ if (readableContent(live.get(key)) === value) {
352
376
  continue
353
377
  }
354
378
  written.push(key)
379
+ // The client refuses it too; checking here lets a dry run report what the real write would refuse.
380
+ assertServiceEnvValueFits(key, value)
355
381
  if (input.dryRun) {
356
382
  continue
357
383
  }
358
384
  assertRunning(collaborators.signal)
359
- await collaborators.api.putServiceEnv({ serviceId: service.id, key, value, signal: collaborators.signal })
385
+ await collaborators.api.putServiceEnv({ serviceId: service.id, key, value, sensitive: false, signal: collaborators.signal })
360
386
  }
361
387
  return written
362
388
  }
363
389
 
390
+ /**
391
+ * Delete, unread, the manifest copy older releases wrote sensitive: nothing reads it, as each proxy version serves
392
+ * the file its own build wrote, and the platform proxy fronts only the platform's apps (ADR-0034). True if one existed.
393
+ */
394
+ const removeLegacyProxyManifest = async (
395
+ proxyEnv: LiveServiceEnv,
396
+ input: PlatformDeployInput,
397
+ collaborators: PlatformDeployCollaborators,
398
+ ): Promise<boolean> => {
399
+ const legacy = proxyEnv.get(FABRIKA_PROXY_MANIFEST_JSON)
400
+ if (legacy === undefined) {
401
+ return false
402
+ }
403
+ if (!input.dryRun) {
404
+ assertRunning(collaborators.signal)
405
+ await collaborators.api.deleteServiceEnv({ envId: legacy.id, signal: collaborators.signal })
406
+ }
407
+ return true
408
+ }
409
+
364
410
  /** Only a known rejection permits another request against the same reservation. */
365
411
  const triggerPipeline = async (
366
412
  service: ResolvedService,
@@ -434,11 +480,12 @@ export const deployPlatformService = async (
434
480
  return
435
481
  }
436
482
  prepareZeropsDescriptor(input.source.descriptor.contents, service.hostname)
483
+ const archive = await serviceArchive(service, input)
437
484
  const reserved = await api.createAppVersion({ serviceId: service.id, name: input.source.commitSha, signal })
438
485
  const appVersionId = reserved.id
439
486
  log.info(`${service.hostname}: app-version ${appVersionId}, commit ${input.source.commitSha}, setup ${service.hostname}`)
440
487
  try {
441
- await uploadPlatformArchive(reserved.uploadUrl, input.source.archivePath, signal, collaborators.uploadFetch)
488
+ await uploadPlatformArchive(reserved.uploadUrl, archive, signal, collaborators.uploadFetch)
442
489
  assertRunning(signal)
443
490
  } catch {
444
491
  await cleanupReservation(service, appVersionId, collaborators)
@@ -502,7 +549,8 @@ const deployConcurrently = async (
502
549
  * The decision is the READ-BACK and never the call returning: on an already-published service the
503
550
  * platform answers 2xx and then fails the process it hands back, and a freshly-enabled one can still
504
551
  * read `false` on the next read. Both live-verified. Runs on every deploy, so a subdomain someone
505
- * turned off in the GUI comes back, and throws rather than leaving the installation unreachable.
552
+ * turned off in the GUI comes back, and throws rather than leaving the installation unreachable. The
553
+ * HTTP ports lag a fresh ACTIVE version, so `serviceStackIsNotHttp` is retried for a bounded window first.
506
554
  */
507
555
  const ensureSubdomainAccess = async (
508
556
  proxy: ResolvedService,
@@ -519,11 +567,11 @@ const ensureSubdomainAccess = async (
519
567
  return
520
568
  }
521
569
  try {
522
- await api.enableSubdomainAccess({ serviceId: proxy.id, signal })
570
+ await enableSubdomainAccessWhenHttp({ api, serviceId: proxy.id, sleep, signal })
523
571
  } catch (error) {
524
572
  if (error instanceof ZeropsApiError && error.code === ZEROPS_SERVICE_NOT_HTTP) {
525
573
  throw new Error(
526
- `the proxy cannot be published on a zerops.app subdomain: it exposes no deployed HTTP port (${ZEROPS_SERVICE_NOT_HTTP})`,
574
+ `the proxy cannot be published on a zerops.app subdomain: it still exposes no deployed HTTP port (${ZEROPS_SERVICE_NOT_HTTP}) minutes after its deploy`,
527
575
  )
528
576
  }
529
577
  throw error
@@ -569,6 +617,7 @@ export const deployPlatform = async (
569
617
  const source = await collaborators.prepareSource(input.sourceDir, collaborators.signal)
570
618
  try {
571
619
  validateInstallationSetups(source, PLATFORM_DEPLOY_ORDER)
620
+ await assertCheckerPublished(collaborators.checkerPublished, collaborators.signal)
572
621
  await deployPreparedPlatform(input, collaborators, source)
573
622
  } finally {
574
623
  await source.dispose()
@@ -617,26 +666,18 @@ export const deployPreparedPlatform = async (
617
666
  ? 'MCP authoring: off'
618
667
  : `MCP authoring: on, ${oauthConfig.clients.length} client(s), resource(s) ${oauthConfig.resources.map((entry) => entry.resource).join(', ')}`,
619
668
  )
620
- const merged = mergePlatformProxyManifest(platformManifest, readLiveProxyManifest(proxyEnv.get(FABRIKA_PROXY_MANIFEST_JSON)))
621
- log.info(`${merged.manifest.apps.length} app(s): ${merged.manifest.apps.map((app) => app.id).join(', ')}`)
622
- if (merged.carried.length > 0) {
623
- log.info(`carried through from the live manifest: ${merged.carried.join(', ')}`)
624
- }
625
- for (const displaced of merged.superseded) {
626
- log.warn(
627
- `the live manifest entry \`${displaced.id}\` stood on the platform host ${displaced.host} and was replaced — `
628
- + 'that host now answers for the platform app that owns it',
629
- )
630
- }
669
+ log.info(`${platformManifest.apps.length} app(s): ${platformManifest.apps.map((app) => app.id).join(', ')}`)
631
670
 
632
671
  log.step('Write the service environment')
633
672
  const controlTableDigest = platformServiceTableDigest(platformManifest, platformProxyAppFor(PLATFORM_PROXY_MANIFEST_TEMPLATE, 'control').id)
634
- const desired = desiredServiceEnv(environment, placement, encodeProxyManifestJson(merged.manifest), proxySource, oauthConfig, controlTableDigest)
673
+ const desired = desiredServiceEnv(environment, placement, proxySource, oauthConfig, controlTableDigest)
635
674
  for (const hostname of PLATFORM_DEPLOY_ORDER) {
636
675
  const service = serviceOf(services, hostname)
637
676
  const live = hostname === PLATFORM_PROXY_SERVICE ? proxyEnv : await readServiceEnv(service.id, collaborators)
638
677
  const keys = desired.get(hostname) ?? new Map<string, string>()
639
- if (hostname === 'iam' && oauthConfig === undefined && (live.get(IAM_OAUTH_CONFIG) ?? '').trim() !== '') {
678
+ const liveOAuthConfig = live.get(IAM_OAUTH_CONFIG)
679
+ const oauthStillSet = liveOAuthConfig !== undefined && (liveOAuthConfig.sensitive || liveOAuthConfig.content.trim() !== '')
680
+ if (hostname === 'iam' && oauthConfig === undefined && oauthStillSet) {
640
681
  log.warn(`iam: MCP authoring is off but ${IAM_OAUTH_CONFIG} is still set; IAM keeps issuing MCP tokens until it is removed`)
641
682
  }
642
683
  const written = await applyServiceEnv(service, keys, live, input, collaborators)
@@ -646,12 +687,19 @@ export const deployPreparedPlatform = async (
646
687
  : `${hostname}: ${input.dryRun ? 'would write' : 'wrote'} ${written.join(', ')}`,
647
688
  )
648
689
  }
690
+ if (await removeLegacyProxyManifest(proxyEnv, input, collaborators)) {
691
+ log.info(
692
+ `${PLATFORM_PROXY_SERVICE}: ${input.dryRun ? 'would remove' : 'removed'} ${FABRIKA_PROXY_MANIFEST_JSON}, the copy of the manifest a `
693
+ + 'release before this one kept there',
694
+ )
695
+ }
649
696
 
650
- // EVERY deploy is after EVERY write. That is what makes the sequence fail closed: a manifest this
651
- // command could not apply leaves the previous manifest in front of the previous code, never in front
652
- // of new code — and `ENVIRONMENT` is in place before any service that refuses `local` restarts.
697
+ // EVERY deploy is after EVERY write, and the manifest rides in the proxy's own archive. That is what makes
698
+ // the sequence fail closed: a proxy build that does not go ACTIVE leaves the previous manifest in front of
699
+ // the previous code, because control builds only after it — and `ENVIRONMENT` is in place before any
700
+ // service that refuses `local` restarts.
653
701
  log.step(`Deploy ${PLATFORM_CONCURRENT_DEPLOY.join(' + ')} together, then ${PLATFORM_SEQUENTIAL_DEPLOY.join(' → ')}`)
654
- const buildInput = { source, dryRun: input.dryRun }
702
+ const buildInput = { source, dryRun: input.dryRun, proxyManifest: encodeProxyManifestJson(platformManifest) }
655
703
  await deployConcurrently(services, buildInput, collaborators)
656
704
  for (const hostname of PLATFORM_SEQUENTIAL_DEPLOY) {
657
705
  await deployPlatformService(serviceOf(services, hostname), buildInput, collaborators)
@@ -669,11 +717,12 @@ export const deployPreparedPlatform = async (
669
717
  log.info('custom-domain installation: the operator binds each domain to the project balancer; no subdomain is published')
670
718
  }
671
719
 
672
- log.step('Register the console with IAM')
720
+ log.step('Register the console and IAM with IAM')
673
721
  const consoleApp = platformProxyAppFor(PLATFORM_PROXY_MANIFEST_TEMPLATE, 'control')
674
722
  const consoleOrigin = platformOrigin(placement.scheme, placement.hosts.control)
675
723
  if (input.dryRun) {
676
724
  log.info(`would reconcile \`${consoleApp.id}\` and register its return origin ${consoleOrigin}`)
725
+ log.info(`would reconcile \`${IAM_APP_ID}\`'s own vocabulary`)
677
726
  } else {
678
727
  // The schema PUT is what REGISTERS the app, and `apps.setReturnOrigins` 404s for an app IAM has
679
728
  // never heard of — so both travel in this one call, in that order (ADR-0022/ADR-0023).
@@ -686,6 +735,15 @@ export const deployPreparedPlatform = async (
686
735
  signal,
687
736
  })
688
737
  log.info(`\`${consoleApp.id}\` reconciled; return origin ${consoleOrigin}`)
738
+ // IAM's own vocabulary, which `user-admin` is granted from (ADR-0058). It has no return origin.
739
+ await collaborators.reconcileSchema({
740
+ url: platformOrigin(placement.scheme, placement.hosts.iam),
741
+ app: IAM_APP_ID,
742
+ schema: IAM_APP_SCHEMA,
743
+ adminKey: input.iamAdminKey,
744
+ signal,
745
+ })
746
+ log.info(`\`${IAM_APP_ID}\` reconciled`)
689
747
  }
690
748
 
691
749
  log.step(input.dryRun ? 'Dry run complete — nothing was written' : 'Installation deployed')
@@ -11,6 +11,7 @@
11
11
  import type { AppSchema } from '@fabrika/auth-core'
12
12
 
13
13
  export const PLATFORM_CONSOLE_APP_SCHEMA: AppSchema = {
14
+ displayName: 'Console',
14
15
  scopes: [
15
16
  { type: 'app', label: 'App' },
16
17
  { type: 'environment', label: 'Environment' },
@@ -31,14 +32,29 @@ export const PLATFORM_CONSOLE_APP_SCHEMA: AppSchema = {
31
32
  { action: 'operations.manage', description: 'Manage Operations sources, alerts, and retention' },
32
33
  ],
33
34
  roles: {
35
+ observer: {
36
+ name: 'Observer',
37
+ description: 'Read Operations errors and activity.',
38
+ permissions: ['operations.read'],
39
+ },
40
+ triager: {
41
+ name: 'Triager',
42
+ description: 'Read and triage Operations errors.',
43
+ permissions: ['operations.read', 'operations.triage'],
44
+ },
45
+ deployer: {
46
+ name: 'Deployer',
47
+ description: 'Trigger, read and cancel deploys (no registry, namespace or secret management).',
48
+ permissions: ['deploy.*'],
49
+ },
34
50
  operator: {
35
51
  name: 'Operator',
36
- description: 'Trigger and read any deploy (no registry or secret management).',
52
+ description: 'Deploy, and read, triage and manage Operations (no registry or secret management).',
37
53
  permissions: ['deploy.*', 'operations.*'],
38
54
  },
39
55
  admin: {
40
56
  name: 'Admin',
41
- description: 'Full access to every vozka action in every scope.',
57
+ description: 'Every console action in every scope.',
42
58
  permissions: ['*'],
43
59
  },
44
60
  },
@@ -278,6 +278,16 @@ export const PLATFORM_PROXY_MANIFEST_TEMPLATE: PlatformProxyManifestTemplate = {
278
278
  admission: null,
279
279
  oauth: null,
280
280
  },
281
+ {
282
+ id: 'github-setup',
283
+ kind: 'service',
284
+ method: 'GET',
285
+ path: '/api/source/github/setup/:connectionId',
286
+ callers: ['human'],
287
+ requirement: { kind: 'authenticated' },
288
+ admission: null,
289
+ oauth: null,
290
+ },
281
291
  {
282
292
  id: 'rpc',
283
293
  kind: 'service',
@@ -453,6 +463,16 @@ export const PLATFORM_PROXY_MANIFEST_TEMPLATE: PlatformProxyManifestTemplate = {
453
463
  admission: null,
454
464
  oauth: null,
455
465
  },
466
+ {
467
+ id: 'github-setup',
468
+ kind: 'service',
469
+ method: 'GET',
470
+ path: '/api/source/github/setup/:connectionId',
471
+ callers: ['human'],
472
+ requirement: { kind: 'authenticated' },
473
+ admission: null,
474
+ oauth: null,
475
+ },
456
476
  {
457
477
  id: 'rpc',
458
478
  kind: 'service',