@kici-dev/orchestrator 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/agent/dispatcher.d.ts +10 -1
  2. package/dist/agent/host-roster.d.ts +8 -3
  3. package/dist/agent/registry.d.ts +60 -5
  4. package/dist/app.d.ts +10 -5
  5. package/dist/cache/dep-cache.d.ts +14 -6
  6. package/dist/cache/global-eval-round-cache.d.ts +1 -1
  7. package/dist/cache/index.d.ts +1 -1
  8. package/dist/cache/source-cache.d.ts +92 -15
  9. package/dist/cancel/cancel-run.d.ts +20 -2
  10. package/dist/cancel/sweep-stuck-cancelling.d.ts +30 -0
  11. package/dist/cli/api-client.d.ts +10 -0
  12. package/dist/cli/commands/agent-service/logs.d.ts +1 -1
  13. package/dist/cli/commands/agent-service/restart.d.ts +1 -1
  14. package/dist/cli/commands/agent-service/start.d.ts +1 -1
  15. package/dist/cli/commands/agent-service/status.d.ts +1 -1
  16. package/dist/cli/commands/agent-service/stop.d.ts +1 -1
  17. package/dist/cli/commands/agent-service/uninstall.d.ts +1 -1
  18. package/dist/cli/commands/agent.d.ts +25 -0
  19. package/dist/cli/commands/config.d.ts +16 -0
  20. package/dist/cli/commands/db-backup.d.ts +46 -0
  21. package/dist/cli/commands/held-run.d.ts +9 -0
  22. package/dist/cli/commands/orchestrator-service/drain.d.ts +2 -19
  23. package/dist/cli/commands/orchestrator-service/index.d.ts +9 -1
  24. package/dist/cli/commands/orchestrator-service/install-env.d.ts +43 -2
  25. package/dist/cli/commands/orchestrator-service/logs.d.ts +1 -1
  26. package/dist/cli/commands/orchestrator-service/restart.d.ts +1 -1
  27. package/dist/cli/commands/orchestrator-service/start.d.ts +1 -1
  28. package/dist/cli/commands/orchestrator-service/status.d.ts +17 -1
  29. package/dist/cli/commands/orchestrator-service/stop.d.ts +1 -1
  30. package/dist/cli/commands/orchestrator-service/uninstall.d.ts +1 -1
  31. package/dist/cli/commands/orchestrator-service/upgrade.d.ts +2 -1
  32. package/dist/cli/commands/rotate.d.ts +1 -1
  33. package/dist/cli/commands/shared/env-file-mode.d.ts +16 -0
  34. package/dist/cli/commands/shared/upgrade-hooks.d.ts +32 -0
  35. package/dist/cli/commands/shared/upgrade-safety.d.ts +81 -0
  36. package/dist/cli/commands/shared/versioned-upgrade.d.ts +67 -12
  37. package/dist/cli/commands/source.d.ts +1 -0
  38. package/dist/cli/commands/token.d.ts +4 -0
  39. package/dist/cli/commands/trust-policy.d.ts +57 -1
  40. package/dist/cli/join.d.ts +1 -1
  41. package/dist/cli/service/backup-timer.d.ts +108 -0
  42. package/dist/cli/service/compose-path.d.ts +11 -0
  43. package/dist/cli/service/compose.d.ts +22 -0
  44. package/dist/cli/service/deploy-env.d.ts +10 -11
  45. package/dist/cli/service/env-assignment.d.ts +24 -0
  46. package/dist/cli/service/index.d.ts +2 -2
  47. package/dist/cli/service/instance/resolve.d.ts +71 -7
  48. package/dist/cli/service/instance/types.d.ts +15 -0
  49. package/dist/cli/service/launchd.d.ts +7 -0
  50. package/dist/cli/service/systemd.d.ts +1 -0
  51. package/dist/cli/service/types.d.ts +36 -0
  52. package/dist/cli/service/windows.d.ts +16 -0
  53. package/dist/cli/wizard/orchestrator-wizard.d.ts +12 -3
  54. package/dist/cli/wizard/prompts.d.ts +7 -0
  55. package/dist/cli.js +12525 -9298
  56. package/dist/cluster/cluster-settings-reader.d.ts +2 -2
  57. package/dist/cluster/coordinator.d.ts +11 -0
  58. package/dist/cluster/instance-heartbeat.d.ts +96 -0
  59. package/dist/cluster/join-client.d.ts +55 -5
  60. package/dist/cluster/join-handler.d.ts +1 -0
  61. package/dist/cluster/orphan-recovery.d.ts +16 -0
  62. package/dist/cluster/peer-registry.d.ts +10 -0
  63. package/dist/concurrency/dispatch-next-queued.d.ts +1 -13
  64. package/dist/concurrency/index.d.ts +1 -2
  65. package/dist/concurrency/queue-manager.d.ts +63 -18
  66. package/dist/config/env-overlay.d.ts +3 -2
  67. package/dist/config.d.ts +24 -0
  68. package/dist/contexts/release-queued-holds.d.ts +23 -1
  69. package/dist/dashboard/attestation-filters.d.ts +2 -0
  70. package/dist/dashboard/handler.d.ts +22 -2
  71. package/dist/db/deferred-indexes.d.ts +72 -0
  72. package/dist/db/migration-test-harness.d.ts +22 -0
  73. package/dist/db/migrations/131_execution_jobs_git_credentials.d.ts +30 -0
  74. package/dist/db/migrations/132_execution_runs_trigger_event.d.ts +23 -0
  75. package/dist/db/migrations/133_dispatch_queue_source_tar_digest.d.ts +23 -0
  76. package/dist/db/migrations/134_dispatch_queue_ownership.d.ts +4 -0
  77. package/dist/db/migrations/135_cluster_settings_retention_days.d.ts +4 -0
  78. package/dist/db/migrations/136_master_key_wrapped_tables_key_version.d.ts +4 -0
  79. package/dist/db/migrations/137_execution_runs_pr_head_context.d.ts +4 -0
  80. package/dist/db/migrations/138_execution_runs_subject_trigger_event.d.ts +4 -0
  81. package/dist/db/migrations/139_admin_token_subject.d.ts +4 -0
  82. package/dist/db/migrations/140_dispatch_queue_canonical_labels.d.ts +54 -0
  83. package/dist/db/migrations/141_event_log_shed.d.ts +15 -0
  84. package/dist/db/migrator.d.ts +47 -0
  85. package/dist/db/notify-listener.d.ts +60 -0
  86. package/dist/db/synthetic-job-ids.d.ts +24 -0
  87. package/dist/db/types.d.ts +169 -4
  88. package/dist/deployment/deployment-identity.d.ts +31 -1
  89. package/dist/diagnostics/checks/index.d.ts +2 -1
  90. package/dist/diagnostics/checks/retention.d.ts +24 -0
  91. package/dist/diagnostics/types.d.ts +7 -0
  92. package/dist/events/event-router.d.ts +1 -1
  93. package/dist/fault-injection-types.d.ts +53 -0
  94. package/dist/firecracker/host-network.d.ts +63 -8
  95. package/dist/git/credential-broker.d.ts +35 -2
  96. package/dist/git/inherited-secret.d.ts +7 -2
  97. package/dist/git/job-context.d.ts +7 -4
  98. package/dist/helpers/secure-write.d.ts +35 -0
  99. package/dist/index.js +196 -67
  100. package/dist/metrics/prometheus.d.ts +116 -1
  101. package/dist/oidc/db-signer.d.ts +27 -5
  102. package/dist/oidc/id-token-claims.d.ts +33 -2
  103. package/dist/oidc/oidc-mint-registration.d.ts +6 -0
  104. package/dist/oidc/orchestrator-mint.d.ts +8 -0
  105. package/dist/oidc/reconcile-signing-key.d.ts +37 -0
  106. package/dist/orchestrator-core.d.ts +9 -3
  107. package/dist/pipeline/decorating-secret-resolver.d.ts +1 -1
  108. package/dist/pipeline/dispatch-matched-workflow.d.ts +65 -14
  109. package/dist/pipeline/manual-schedule.d.ts +1 -0
  110. package/dist/pipeline/process-webhook.d.ts +20 -2
  111. package/dist/pipeline/route-or-dispatch-jobs.d.ts +7 -0
  112. package/dist/pipeline/run-event-context.d.ts +39 -0
  113. package/dist/policy/dashboard-write-policy-listener.d.ts +1 -1
  114. package/dist/policy/dashboard-write-policy.d.ts +47 -1
  115. package/dist/provenance/dispatch-context.d.ts +46 -0
  116. package/dist/provenance/verify-deferred-capture.d.ts +39 -0
  117. package/dist/providers/github/auth.d.ts +9 -0
  118. package/dist/providers/github/deliveries.d.ts +118 -0
  119. package/dist/providers/universal-git/config.d.ts +1 -1
  120. package/dist/queue/cleanup.d.ts +13 -0
  121. package/dist/queue/job-queue.d.ts +162 -14
  122. package/dist/queue/retention.d.ts +105 -0
  123. package/dist/reporting/execution-tracker.d.ts +87 -36
  124. package/dist/routes/admin-db.d.ts +5 -0
  125. package/dist/routes/admin-events.d.ts +13 -0
  126. package/dist/routes/admin-held-runs.d.ts +11 -1
  127. package/dist/routes/admin-org-settings.d.ts +9 -0
  128. package/dist/routes/admin-runs.d.ts +7 -4
  129. package/dist/routes/admin-sources.d.ts +15 -0
  130. package/dist/routes/admin-trust-policy.d.ts +11 -0
  131. package/dist/routes/admin.d.ts +25 -0
  132. package/dist/scaler/backend-factory.d.ts +16 -0
  133. package/dist/scaler/bare-metal-backend.d.ts +22 -4
  134. package/dist/scaler/config.d.ts +6 -3
  135. package/dist/scaler/container-backend.d.ts +102 -3
  136. package/dist/scaler/container-hostconfig.d.ts +60 -0
  137. package/dist/scaler/file-tail.d.ts +25 -2
  138. package/dist/scaler/firecracker-api.d.ts +9 -1
  139. package/dist/scaler/firecracker-backend.d.ts +135 -2
  140. package/dist/scaler/host-access.d.ts +59 -0
  141. package/dist/scaler/index.d.ts +1 -0
  142. package/dist/scaler/manager.d.ts +33 -0
  143. package/dist/scaler/scaler-state-store.d.ts +6 -0
  144. package/dist/scaler/types.d.ts +5 -7
  145. package/dist/secrets/config.d.ts +48 -0
  146. package/dist/secrets/dashboard-encryption-key.d.ts +19 -0
  147. package/dist/secrets/ephemeral-keys.d.ts +9 -3
  148. package/dist/secrets/index.d.ts +3 -1
  149. package/dist/secrets/job-secret-gate.d.ts +76 -0
  150. package/dist/secrets/master-key-rotation.d.ts +65 -0
  151. package/dist/secrets/secret-output-crypto.d.ts +17 -0
  152. package/dist/secrets/secret-resolver.d.ts +23 -6
  153. package/dist/secrets/source-credentials.d.ts +1 -1
  154. package/dist/secrets/token-manager.d.ts +4 -1
  155. package/dist/security/reduced-privilege-note.d.ts +5 -0
  156. package/dist/security/trust-policy-gate.d.ts +70 -6
  157. package/dist/security/trust-tier.d.ts +31 -0
  158. package/dist/server.d.ts +1 -1
  159. package/dist/server.js +10661 -5282
  160. package/dist/sources/source-manager.d.ts +1 -1
  161. package/dist/stale-detector/stale-dispatch-candidates.d.ts +43 -0
  162. package/dist/standalone.js +9258 -4122
  163. package/dist/storage/loopback-guard.d.ts +53 -6
  164. package/dist/webhook/event-log.d.ts +14 -1
  165. package/dist/webhook/generic-sources-listener.d.ts +1 -1
  166. package/dist/webhook/ingest-admission.d.ts +24 -0
  167. package/dist/webhook/ingest-overflow-replayer.d.ts +78 -14
  168. package/dist/webhook/ingest-overflow-types.d.ts +13 -1
  169. package/dist/webhook/register-source-bundle.d.ts +23 -0
  170. package/dist/webhook/relay-reinject.d.ts +11 -12
  171. package/dist/webhook/shed-breadcrumb.d.ts +15 -0
  172. package/dist/ws/git-credential-relay.d.ts +41 -12
  173. package/dist/ws/platform-client.d.ts +28 -5
  174. package/installer-image-digests.json +3 -3
  175. package/package.json +4 -4
  176. package/sbom.spdx.json +53 -53
  177. package/dist/concurrency/group-tracker.d.ts +0 -51
  178. package/dist/scaler/nftables.d.ts +0 -111
@@ -8,7 +8,7 @@
8
8
  * differs only in the local sentinel issuer (`kici-local`) and the org anchor
9
9
  * (`__default__`, the plane's default org).
10
10
  */
11
- import { AttestationOrigin, SourceOrigin } from '@kici-dev/engine';
11
+ import { AttestationOrigin, SourceOrigin, type EventClaims } from '@kici-dev/engine';
12
12
  /** The subset of an execution_runs row the claims need. */
13
13
  export interface RunClaimSource {
14
14
  run_id: string;
@@ -20,6 +20,24 @@ export interface RunClaimSource {
20
20
  provider: string | null;
21
21
  /** True when the run executed an uploaded local working tree (`kici run remote`). */
22
22
  local_working_tree: boolean;
23
+ /** Normalized event that started the run (`push`, `pull_request:opened`, …). */
24
+ trigger_event?: string | null;
25
+ /**
26
+ * The event the SUBJECT is derived from, when that is not `trigger_event`.
27
+ * A re-run carries the original run's event here. NULL means "use
28
+ * `trigger_event`". Read only by `buildIdTokenSubject`.
29
+ */
30
+ subject_trigger_event?: string | null;
31
+ /** Pull-request HEAD branch. */
32
+ head_ref?: string | null;
33
+ /** `owner/repo` of the pull-request HEAD. */
34
+ head_repository?: string | null;
35
+ /** True for a fork pull request; NULL when the run resolved no answer. */
36
+ is_fork?: boolean | null;
37
+ /** Resolved trust tier for the triggering actor. */
38
+ trust_tier?: string | null;
39
+ /** Provider login of the triggering actor. */
40
+ trigger_actor_username?: string | null;
23
41
  }
24
42
  /** The subset of an execution_jobs row the claims need. */
25
43
  export interface JobClaimSource {
@@ -43,8 +61,21 @@ export interface BuildClaimsOpts {
43
61
  statementHash: string;
44
62
  origin: 'deferred' | 'offline-backfill';
45
63
  };
64
+ /**
65
+ * Restore the pre-split pull-request `sub` — the branch-shaped form that
66
+ * collides with a push to the same base branch. An escape hatch for an
67
+ * operator whose cloud trust policy breaks mid-migration, set by
68
+ * `KICI_OIDC_LEGACY_PR_SUB`.
69
+ *
70
+ * @deprecated Removed at v1.0.0. Move the policy to `repo:<repo>:pull_request`.
71
+ */
72
+ legacyPullRequestSubject?: boolean;
46
73
  }
47
- export interface IdTokenClaims {
74
+ /**
75
+ * Extends {@link EventClaims}, so every token carries the event-context claims
76
+ * a cloud trust policy needs to tell a fork pull request from a push.
77
+ */
78
+ export interface IdTokenClaims extends EventClaims {
48
79
  iss: string;
49
80
  sub: string;
50
81
  aud: string;
@@ -56,6 +56,12 @@ export interface OidcMintRegistrationInput {
56
56
  };
57
57
  db: Kysely<Database>;
58
58
  orchestratorId: string;
59
+ /**
60
+ * `KICI_OIDC_LEGACY_PR_SUB` — restore the pre-split pull-request `sub`.
61
+ *
62
+ * @deprecated Removed at v1.0.0.
63
+ */
64
+ legacyPullRequestSubject?: boolean;
59
65
  /**
60
66
  * Test-only fault-injection predicate forwarded to the relay mint handler,
61
67
  * supplied only by the build-time test double. Only ever defers — the
@@ -36,6 +36,14 @@ export interface OrchestratorMintDeps {
36
36
  ttlSeconds?: number;
37
37
  /** Injectable clock (seconds). Defaults to wall-clock. */
38
38
  nowSeconds?: () => number;
39
+ /**
40
+ * Restore the pre-split pull-request `sub`, from `KICI_OIDC_LEGACY_PR_SUB`.
41
+ * An escape hatch for an operator whose cloud trust policy breaks mid-
42
+ * migration; leaves the collision with a same-branch push in place.
43
+ *
44
+ * @deprecated Removed at v1.0.0.
45
+ */
46
+ legacyPullRequestSubject?: boolean;
39
47
  }
40
48
  export interface OrchestratorMintInput {
41
49
  runId: string;
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Boot reconcile for the orchestrator's provenance signing key. Ensures exactly
3
+ * one `active` key exists and returns a live `Signer` bound to it.
4
+ *
5
+ * - `db` custody: the private key is a random keypair generated + persisted
6
+ * (master-key-wrapped) in the DB. Generation is LEADER-GATED so an HA cluster
7
+ * never races two active keys; every node then loads the one active row. A
8
+ * non-leader that finds no active row yet returns null this tick and is
9
+ * retried on the next leadership/boot cycle.
10
+ * - `aws-kms` / `command` custody: the key lives outside KiCI, so every node
11
+ * resolves the SAME kid and `upsertActive` is idempotent across the fleet.
12
+ */
13
+ import type { JWK } from 'jose';
1
14
  import { type OrchestratorSignerConfig } from './orchestrator-signer-factory.js';
2
15
  import type { Signer } from './signer.js';
3
16
  import type { OrchestratorSigningKeyRepo } from '../db/repos/signing-keys-repo.js';
@@ -7,6 +20,30 @@ export interface ReconcileSigningKeyDeps {
7
20
  isLeader: () => boolean;
8
21
  /** The orchestrator master key (`KICI_SECRET_KEY`), for `db` custody wrapping. */
9
22
  secretKey: string | undefined;
23
+ /**
24
+ * The previous master key (`KICI_SECRET_KEY_OLD`), during a rotation grace
25
+ * window. The signing key is master-key wrapped, so a rotation that has not
26
+ * yet swept `orchestrator_signing_keys` leaves the row sealed under the old
27
+ * key — without this fallback the boot path throws and provenance signing is
28
+ * dead until the old key is restored.
29
+ */
30
+ oldSecretKey?: string | undefined;
31
+ /**
32
+ * Re-seal a row that opened only under the OLD master key, back under the
33
+ * current one. Optional: when unset the row is loaded and left as it is, so
34
+ * signing works but stays stranded. Best-effort — a failure is warned and
35
+ * never blocks the boot.
36
+ *
37
+ * This is the recovery path for a deployment stranded by a rotation that ran
38
+ * before `orchestrator_signing_keys` was part of the sweep: restore the old
39
+ * key once, restart, and the row moves to the current key on its own.
40
+ */
41
+ selfHeal?: (row: {
42
+ kid: string;
43
+ key_version: number;
44
+ }, privateJwk: JWK) => Promise<void>;
45
+ /** Structured warn sink for the self-heal notice. */
46
+ logWarn?: (message: string, meta: Record<string, unknown>) => void;
10
47
  /** Called once when a NEW kid is first activated (audit log, system actor). */
11
48
  audit: (info: {
12
49
  kid: string;
@@ -47,7 +47,7 @@ import { LogWriter } from './reporting/log-writer.js';
47
47
  import { type NormalizedLogChunk } from './reporting/log-chunk-sink.js';
48
48
  import { PeerRegistry, PeerClient, createPeerHandler, RaftNode, OrphanRecovery, RunCoordinator, PlanHeadroomStore } from './cluster/index.js';
49
49
  import { SourceStore, SourceManager } from './sources/index.js';
50
- import { PgSecretStore, AuditLogger, SecretResolver } from './secrets/index.js';
50
+ import { type ResolvedMasterKeys, PgSecretStore, AuditLogger, SecretResolver } from './secrets/index.js';
51
51
  import type { SecretStore } from '@kici-dev/engine';
52
52
  import type { AdminRouteDeps } from './routes/admin.js';
53
53
  import { AgentTokenStore } from './agent/token-store.js';
@@ -56,7 +56,7 @@ import { EventStore } from './events/event-store.js';
56
56
  import { EventRouter, type EventMatchContext } from './events/event-router.js';
57
57
  import { EventEmitter } from './events/event-emitter.js';
58
58
  import { TrustStore } from './events/trust-store.js';
59
- import type { OrchestratorFaultInjection } from './testing/fault-injection.js';
59
+ import type { OrchestratorFaultInjection } from './fault-injection-types.js';
60
60
  import { GenericSourceManager } from './webhook/generic-sources.js';
61
61
  import { GenericSourcesChangeListener } from './webhook/generic-sources-listener.js';
62
62
  import { DashboardWritePolicyChangeListener } from './policy/dashboard-write-policy-listener.js';
@@ -392,7 +392,7 @@ export declare function buildMatrixOutputsEnvelope(baseName: string, children: A
392
392
  * upstream-outputs read doesn't block dispatch — the agent will still get
393
393
  * its declared secrets, just without the merged upstream additions.
394
394
  */
395
- export declare function mergeUpstreamOutputs(db: Kysely<Database>, runId: string, jobName: string, needs: unknown, dispatchSecrets: Record<string, string> | undefined, secretKey: string): Promise<{
395
+ export declare function mergeUpstreamOutputs(db: Kysely<Database>, runId: string, jobName: string, needs: unknown, dispatchSecrets: Record<string, string> | undefined, masterKeys: ResolvedMasterKeys | null): Promise<{
396
396
  mergedSecrets: Record<string, string> | undefined;
397
397
  upstreamJobOutputs: Record<string, Record<string, unknown>> | undefined;
398
398
  upstreamJobStatuses: Record<string, ExecutionJobStatus> | undefined;
@@ -434,5 +434,11 @@ export declare function bootstrapOrchestrator(config: AppConfig, hooks: Orchestr
434
434
  * drive the seams directly.
435
435
  */
436
436
  faultInjection?: OrchestratorFaultInjection;
437
+ /**
438
+ * This build's version, recorded on the coordinator heartbeat row. Read by
439
+ * an operator during a rolling upgrade to see which coordinators have
440
+ * already moved. Undefined leaves the column NULL.
441
+ */
442
+ orchestratorVersion?: string;
437
443
  }): Promise<void>;
438
444
  //# sourceMappingURL=orchestrator-core.d.ts.map
@@ -23,7 +23,7 @@ export declare class DecoratingSecretResolver implements SecretResolverApi {
23
23
  private readonly cli;
24
24
  constructor(base: SecretResolverApi, cli: CliSecrets);
25
25
  resolveForJob(orgId: string, contextName: string, hostCtx?: HostFacts): Promise<Record<string, string>>;
26
- resolveNamed(orgId: string, scope: string, key: string, opts?: {
26
+ resolveNamedInternal(orgId: string, scope: string, key: string, opts?: {
27
27
  store?: string;
28
28
  runId?: string;
29
29
  jobId?: string;
@@ -14,8 +14,9 @@
14
14
  * results through the pipeline.
15
15
  */
16
16
  import { ExecutionJobStatus, InitFailureCategory, CacheRefScope, HoldScope, TriggerSource } from '@kici-dev/engine';
17
- import type { LabelMatcher, LockWorkflow, LockJob, ApprovalRequirement, HostTargetSelector, SimulatedEvent, WorkflowDecision, MaterializedJob, ResolvedHostAgent, HostFacts } from '@kici-dev/engine';
17
+ import type { LabelMatcher, LockWorkflow, LockJob, ApprovalRequirement, HostTargetSelector, SimulatedEvent, WorkflowDecision, MaterializedJob, ResolvedHostAgent, UpstreamSnapshot, HostFacts } from '@kici-dev/engine';
18
18
  import { type HostRosterStore } from '../agent/host-roster.js';
19
+ import { NEEDS_PENDING_JOB_ID_PREFIX } from '../db/synthetic-job-ids.js';
19
20
  import { gatherInvokeResults } from '../orchestrator-core.js';
20
21
  import type { WebhookInfo } from '../webhook/handler.js';
21
22
  import type { ProviderBundle } from '../provider-registry.js';
@@ -24,6 +25,7 @@ import type { QueuedJobInput } from '../queue/job-queue.js';
24
25
  import type { TrustResolution } from '../security/trust-resolver.js';
25
26
  import type { TrustPolicyOutcome } from '../security/trust-policy-gate.js';
26
27
  import type { CreateHeldRunData } from '../contexts/held-runs.js';
28
+ import { type NpmRegistrySpec } from './install-secrets-resolver.js';
27
29
  import { type ProcessingDeps } from './processor.js';
28
30
  /**
29
31
  * Trusted refs (write+ contributor, default-branch) get the org-shared cache
@@ -82,26 +84,34 @@ export interface WorkflowDispatchContext {
82
84
  fullLockFile: {
83
85
  workflows: readonly LockWorkflow[];
84
86
  lockfileHash?: string;
87
+ /** In-repo `workspace:` sibling closure digest; part of the dep pointer key. */
88
+ siblingsDigest?: string;
85
89
  source: {
86
90
  file: string;
87
91
  };
88
92
  };
89
93
  resolvedOrgId: string;
90
94
  /**
91
- * Siblings admitted against a context concurrency limit during THIS dispatch
92
- * pass, keyed by {@link concurrencyAdmissionKey}.
95
+ * The NAMES of the jobs this dispatch pass has admitted against a context
96
+ * concurrency limit, keyed by {@link concurrencyAdmissionKey}.
93
97
  *
94
- * The gate's running count counts jobs whose status is already `running`, so
95
- * it cannot see anything this pass has only just admitted. Without this tally
96
- * every child of a fan-out is evaluated against that one blind value and all
97
- * N are admitted against a single slot.
98
+ * The database count cannot see a job this pass has only just admitted, so
99
+ * without this tally every child of a fan-out is evaluated against that one
100
+ * blind value and all N are admitted against a single slot.
101
+ *
102
+ * Names rather than a counter, because the two terms have to be DISJOINT: the
103
+ * count query excludes exactly the names held here (see
104
+ * {@link countOccupyingJobs}), so a job whose row has already landed
105
+ * contributes its tally entry and nothing else. A counter cannot express that
106
+ * exclusion, and summing it with a count that already sees the row is how the
107
+ * deferred-init path double-counts a sibling and spuriously holds a job.
98
108
  *
99
109
  * Created lazily on first use and dropped with the ctx. It is per-pass state,
100
- * never persisted and never shared between passes; two concurrent passes
101
- * still race, which is accepted this is a throughput control, not an
102
- * isolation boundary.
110
+ * never persisted and never shared between passes; two actors arriving in the
111
+ * same millisecond still race between the count and the insert, which is
112
+ * accepted — this is a throughput control, not an isolation boundary.
103
113
  */
104
- concurrencyAdmissions?: Map<string, number>;
114
+ concurrencyAdmissions?: Map<string, Set<string>>;
105
115
  workflow: LockWorkflow;
106
116
  decision: WorkflowDecision;
107
117
  runId: string;
@@ -373,6 +383,8 @@ interface DispatchSetup {
373
383
  interface BuildPrepResult {
374
384
  sourceTarUrl: string | undefined;
375
385
  sourceTarHash: string | undefined;
386
+ /** SHA-256 of the source tarball's own bytes; the agent verifies against it. */
387
+ sourceTarDigest: string | undefined;
376
388
  depsUrl: string | undefined;
377
389
  depsHash: string | undefined;
378
390
  contentHash: string | undefined;
@@ -482,8 +494,11 @@ interface JobEnvEvalResult {
482
494
  * keys on it, so it is load-bearing rather than cosmetic — which is what makes
483
495
  * it a sound way to recover the gated set without threading a parallel list
484
496
  * through both the single-orchestrator and cluster dispatch paths.
497
+ *
498
+ * Re-exported from `db/synthetic-job-ids.ts`, which owns it so the concurrency
499
+ * sweep in `contexts/` can exclude these rows without importing this module.
485
500
  */
486
- export declare const NEEDS_PENDING_JOB_ID_PREFIX = "needs-pending-";
501
+ export { NEEDS_PENDING_JOB_ID_PREFIX };
487
502
  interface RejectedJob {
488
503
  jobId: string;
489
504
  jobName: string;
@@ -510,7 +525,7 @@ interface RejectedJob {
510
525
  * cloud-hosted one-shot agent, an air-gapped runner) could not run the job at
511
526
  * all. The `__build__` job already carries `buildSourceNeeded` /
512
527
  * `buildDepsNeeded` separately and the agent already honors both, so a
513
- * deps-only build was implemented and simply unreachable.
528
+ * deps-only build was implemented and unreachable.
514
529
  *
515
530
  * A hash is required alongside its miss: with no hash there is no cache key to
516
531
  * write, so building would produce an artifact nothing could ever look up.
@@ -563,6 +578,16 @@ interface JobRoutingSelectors {
563
578
  runsOnPatterns: LabelMatcher[];
564
579
  excludeLabels: string[];
565
580
  excludePatterns: LabelMatcher[];
581
+ /**
582
+ * The `runsOn` exact values exactly as the lock file spells them, before the
583
+ * canonical fold `partitionMatchers` applies.
584
+ *
585
+ * Only {@link resolveRosterAgentPin} reads this. A bare agent id in `runsOn`
586
+ * is the documented inventory fan-out pattern (`runsOn: [h.agentId]`), and an
587
+ * agent id is not a label: `host_roster.agent_id` is case-sensitive, so the
588
+ * folded form misses every host whose id carries a capital.
589
+ */
590
+ runsOnExactRaw: string[];
566
591
  }
567
592
  /**
568
593
  * Partition a lock job's runsOn / excludeLabels matchers into exact labels (SQL
@@ -606,9 +631,15 @@ export declare function requiredRuntimeLabelsFor(container: unknown): string[];
606
631
  * cross-cluster reroute) — exact parity with how `runsOnAll` children pin. Any
607
632
  * other shape (multi-label, a regex pattern, a non-roster label, or no roster
608
633
  * store) returns null and the caller keeps normal label routing.
634
+ *
635
+ * `runsOnExact` takes the values the lock file spells, NOT the canonical labels
636
+ * every other routing path uses: this looks the value up as an agent id, and
637
+ * agent ids are case-sensitive. Handing it folded labels strands every host
638
+ * whose id carries a capital on the normal label path, where a bare agent id
639
+ * matches nothing.
609
640
  */
610
641
  export declare function resolveRosterAgentPin(args: {
611
- runsOnLabels: string[];
642
+ runsOnExact: string[];
612
643
  runsOnPatterns: LabelMatcher[];
613
644
  hostRosterStore: HostRosterStore | undefined;
614
645
  }): Promise<{
@@ -865,6 +896,12 @@ export interface GeneratedJobConfig {
865
896
  connectedInstanceId?: string | null;
866
897
  /** The matrix combination for this child; absent for non-matrix generated jobs. */
867
898
  matrixValues?: Record<string, unknown>;
899
+ /**
900
+ * The `gitCredentials` map the generator's LOCK entry declared, inherited by
901
+ * this child. Persisted to `execution_jobs.git_credentials` at every dispatch
902
+ * site below, which is what the credential relay authorizes a request against.
903
+ */
904
+ gitCredentials?: Readonly<Record<string, Readonly<Record<string, string>>>>;
868
905
  }
869
906
  /**
870
907
  * Split generated configs into pinned (host-pin dispatch) and unpinned (normal
@@ -876,6 +913,20 @@ export declare function partitionGeneratedConfigsByPin(configs: readonly Generat
876
913
  pinnedConfigs: GeneratedJobConfig[];
877
914
  unpinnedConfigs: GeneratedJobConfig[];
878
915
  };
916
+ export declare function resolveGeneratedJobConfigs(args: {
917
+ ctx: WorkflowDispatchContext;
918
+ workflow: LockWorkflow;
919
+ fullLockFile: WorkflowDispatchContext['fullLockFile'];
920
+ resolvedSecrets: Record<string, string> | undefined;
921
+ resolvedNamespacedSecrets: Record<string, Record<string, string>> | undefined;
922
+ runPublicKeyBase64: string | undefined;
923
+ npmRegistries: NpmRegistrySpec[] | undefined;
924
+ installEnvSecrets: Record<string, string> | undefined;
925
+ generatedJobs: LockJob[];
926
+ dynamicEntry: BuildPrepResult['dynamicEntries'][number];
927
+ /** Frozen upstream snapshot threaded into each generated job's dynamicSource for re-eval. */
928
+ upstreamSnapshot?: UpstreamSnapshot;
929
+ }): Promise<GeneratedJobConfig[]>;
879
930
  /**
880
931
  * Recompute the needs gate for `jobNames` and act on whatever became ready:
881
932
  * open the gate for a satisfied job, terminalize a job an upstream's status
@@ -49,6 +49,7 @@ export declare function buildManualJobConfig(workflow: LockWorkflow, mat: Materi
49
49
  runOn: ("cancelled" | "cancelling" | "drift_dropped" | "failed" | "pending" | "queued" | "recovering" | "running" | "skipped" | "success" | "timed_out_stale" | "unroutable")[];
50
50
  })[];
51
51
  rules: readonly import("@kici-dev/engine").LockRule[] | undefined;
52
+ gitCredentials?: Readonly<Record<string, Readonly<Record<string, string>>>> | undefined;
52
53
  dispatchInputs?: Record<string, unknown> | undefined;
53
54
  contentHash?: string | undefined;
54
55
  resolvedHashFiles?: string[] | undefined;
@@ -22,7 +22,7 @@ import type { OrchestratorMode } from '@kici-dev/engine';
22
22
  import type { WebhookInfo } from '../webhook/handler.js';
23
23
  import type { ProviderBundle } from '../provider-registry.js';
24
24
  import { type TrustResolution } from '../security/trust-resolver.js';
25
- import { type TrustPolicyOutcome } from '../security/trust-policy-gate.js';
25
+ import { type ForkSwitchContext, type TrustPolicyOutcome } from '../security/trust-policy-gate.js';
26
26
  import { type WorkflowModification } from '../security/workflow-diff.js';
27
27
  import type { IdentityLink, PermissionLevel } from '../security/identity-link.js';
28
28
  import { type ProcessingDeps } from './processor.js';
@@ -143,6 +143,20 @@ export declare function applyWorkflowModificationsAndSecurityHold(args: {
143
143
  ref: string;
144
144
  credentials: Record<string, unknown>;
145
145
  }): SecurityState;
146
+ /**
147
+ * A trust-policy verdict plus the fork switch it was decided under.
148
+ *
149
+ * The first arm is the short-circuit: a non-PR event, or a provider with no
150
+ * fork model, decides `pass` before any policy is read, so there is no switch
151
+ * to report. Every other verdict comes from an actual policy and carries it —
152
+ * which is what lets the drop site read `forkSwitch` without a fallback that
153
+ * would have to invent a source it never observed.
154
+ */
155
+ export type SecurityPolicyDecision = {
156
+ action: 'pass';
157
+ } | (TrustPolicyOutcome & {
158
+ forkSwitch: ForkSwitchContext;
159
+ });
146
160
  /**
147
161
  * Evaluate the org trust policy for a PR event.
148
162
  *
@@ -157,6 +171,10 @@ export declare function applyWorkflowModificationsAndSecurityHold(args: {
157
171
  * The policy read has two distinct failure shapes, and they get distinct
158
172
  * answers: no stored row is the unconfigured default (`FAIL_CLOSED_POLICY`),
159
173
  * while a read that THREW gets `READ_FAILURE_POLICY` — see there for why.
174
+ *
175
+ * The verdict carries the fork switch it was decided under, so the drop site
176
+ * can explain itself. It is absent on the two short-circuit `pass` arms below,
177
+ * which return before any policy is read.
160
178
  */
161
179
  export declare function evaluateSecurityPolicy(args: {
162
180
  deps: ProcessingDeps;
@@ -166,7 +184,7 @@ export declare function evaluateSecurityPolicy(args: {
166
184
  mode: OrchestratorMode;
167
185
  trustResolution: TrustResolution | undefined;
168
186
  isForkPR: boolean;
169
- }): Promise<TrustPolicyOutcome>;
187
+ }): Promise<SecurityPolicyDecision>;
170
188
  /**
171
189
  * Phase J — After per-repo workflows are dispatched, query the global index
172
190
  * for workflows authored in OTHER repos that target this event type via
@@ -34,6 +34,13 @@ export interface DispatchedJobEntry {
34
34
  jobKind?: JobKind;
35
35
  /** For a gate job, its wall-clock timeout in ms (orchestrator-swept). */
36
36
  timeoutMs?: number;
37
+ /**
38
+ * The `gitCredentials` map this job's lock entry declared, verbatim.
39
+ * Persisted to `execution_jobs.git_credentials`, which the credential relay
40
+ * authorizes a workflow-supplied ref against. Dropping it here would refuse
41
+ * every declared credential on a job this path registered.
42
+ */
43
+ gitCredentials?: Readonly<Record<string, Readonly<Record<string, string>>>>;
37
44
  }
38
45
  export interface RejectedJobEntry {
39
46
  jobId: string;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Resolve the pull-request head context an `execution_runs` row records.
3
+ *
4
+ * `execution_runs.ref` is the branch a run PRESENTS — the BASE branch for a
5
+ * pull request — so it says which branch a run targeted and nothing about where
6
+ * the code came from. These three fields are the missing half, and the OIDC
7
+ * mint reads them: without them a fork pull request and a trusted push to the
8
+ * same base branch mint an identical identity.
9
+ */
10
+ import { type SimulatedEvent } from '@kici-dev/engine';
11
+ export interface RunEventContext {
12
+ headRef: string | null;
13
+ headRepository: string | null;
14
+ isFork: boolean | null;
15
+ }
16
+ /**
17
+ * Derive the head context from a normalized event.
18
+ *
19
+ * `isFork` is resolved from the event TYPE, not from the presence of the
20
+ * optional flag. A push, tag or schedule has no fork dimension, so `false` is
21
+ * its true resolved value — not a stand-in. Leaving those unresolved would make
22
+ * every `is_fork = 'false'` cloud trust policy reject legitimate pushes, and the
23
+ * customer's fix for that (accepting `'unresolved'` too) would re-open the hole
24
+ * the claim exists to close.
25
+ *
26
+ * NULL survives for exactly one case: a PR-family event whose payload carries
27
+ * no head repository, so the fork question genuinely has no answer. A lost
28
+ * write leaves all three NULL, which every claim renders as unresolved and a
29
+ * policy that pins them therefore fails closed.
30
+ *
31
+ * The head repository is what decides that case, NOT the presence of the
32
+ * `isForkPR` flag. Every normalizer derives that flag as `head !== base`, which
33
+ * collapses to `false` when the payload names no head repository — a deleted
34
+ * fork, or a trimmed payload. Reading the flag alone would therefore publish
35
+ * `is_fork = 'false'` for a pull request nobody resolved, which is the fail-OPEN
36
+ * direction this claim exists to remove.
37
+ */
38
+ export declare function resolveRunEventContext(event: Pick<SimulatedEvent, 'type' | 'sourceBranch' | 'headRepo' | 'isForkPR'>): RunEventContext;
39
+ //# sourceMappingURL=run-event-context.d.ts.map
@@ -47,7 +47,7 @@ export interface DashboardWritePolicyChangeListenerOptions {
47
47
  }
48
48
  export declare class DashboardWritePolicyChangeListener {
49
49
  private readonly opts;
50
- private client;
50
+ private listener;
51
51
  private readonly pending;
52
52
  private debounceTimer;
53
53
  private readonly debounceMs;
@@ -22,7 +22,7 @@ import { EventEmitter } from 'node:events';
22
22
  import { type Kysely } from 'kysely';
23
23
  import { DashboardWriteOperation, type DashboardWritePolicyMap, type DashboardWritePolicyState } from '@kici-dev/engine/protocol/dashboard-write-operations';
24
24
  import type { Database } from '../db/types.js';
25
- import type { ActorPrincipal } from '@kici-dev/engine';
25
+ import { type ActorPrincipal, type OrchestratorMode } from '@kici-dev/engine';
26
26
  /**
27
27
  * Postgres `LISTEN/NOTIFY` channel carrying policy changes across the
28
28
  * cluster. The payload is the `customer_id` alone — every coordinator
@@ -54,6 +54,18 @@ export declare class DashboardWritePolicyDisabledError extends Error {
54
54
  readonly code: 'operation_disabled';
55
55
  constructor(operation: DashboardWriteOperation, cliEquivalent: string);
56
56
  }
57
+ /**
58
+ * Error thrown by `setDashboardWritePolicy` when a write would leave a context
59
+ * or reviewer hold with no surface that can answer it. Translated into a 409 by
60
+ * the admin route.
61
+ */
62
+ export declare class HeldRunLockoutRefusedError extends Error {
63
+ readonly operations: DashboardWriteOperation[];
64
+ readonly code: 'held_run_lockout_refused';
65
+ constructor(operations: DashboardWriteOperation[]);
66
+ }
67
+ /** Say which operations were refused, and why there is no fallback for them. */
68
+ export declare function heldRunLockoutMessage(operations: readonly DashboardWriteOperation[]): string;
57
69
  /**
58
70
  * Structured response envelope sent back to Platform when a mutating
59
71
  * dashboard.* handler is rejected by the policy gate. Mirrors the
@@ -104,9 +116,16 @@ export declare function assertDashboardWriteAllowed(db: Kysely<Database>, custom
104
116
  *
105
117
  * Emits `'changed'` on `dashboardWritePolicyEvents` with the new map
106
118
  * so the WS publisher can rebroadcast capabilities.
119
+ *
120
+ * Throws {@link HeldRunLockoutRefusedError} when the write would disable a
121
+ * held-run operation on a Platform-attached orchestrator, where no other surface
122
+ * can answer a hold. The check lives here rather than in the admin route so a
123
+ * caller hitting the API directly is refused too.
107
124
  */
108
125
  export declare function setDashboardWritePolicy(db: Kysely<Database>, customerId: string, updates: DashboardWritePolicyMap, options: {
109
126
  actor: ActorPrincipal;
127
+ /** This orchestrator's mode; decides whether a held-run disable is coherent. */
128
+ mode: OrchestratorMode;
110
129
  onChange?: (event: PolicyChangeEvent) => Promise<void>;
111
130
  }): Promise<DashboardWritePolicyMap>;
112
131
  /**
@@ -128,7 +147,34 @@ export interface PolicyChangeEvent {
128
147
  */
129
148
  export declare function resetDashboardWritePolicy(db: Kysely<Database>, customerId: string, options: {
130
149
  actor: ActorPrincipal;
150
+ mode: OrchestratorMode;
131
151
  onChange?: (event: PolicyChangeEvent) => Promise<void>;
132
152
  }): Promise<DashboardWritePolicyMap>;
153
+ /**
154
+ * Report every org whose stored policy already leaves its context and reviewer
155
+ * holds unanswerable.
156
+ *
157
+ * The refusal above only guards FUTURE writes, and the operator hardening
158
+ * postures documented before it existed recommended exactly this disable — so a
159
+ * deployment that followed them is locked out right now and would learn nothing
160
+ * from a check it never trips.
161
+ *
162
+ * Deliberately read-only. Silently re-enabling a write an operator disabled on
163
+ * purpose is a worse failure than the one being reported: the operator chose
164
+ * that posture, and only they can decide whether to keep the lockout, re-enable
165
+ * the operation, or move the orchestrator to independent mode.
166
+ */
167
+ export declare function findHeldRunLockouts(db: Kysely<Database>, mode: OrchestratorMode): Promise<Array<{
168
+ customerId: string;
169
+ operations: DashboardWriteOperation[];
170
+ }>>;
171
+ /**
172
+ * Log one warning per org already in that state, at boot.
173
+ *
174
+ * Swallows its own failure: a policy read that cannot answer must not stop an
175
+ * orchestrator from starting, and the condition it reports is a standing one
176
+ * that the next boot reports again.
177
+ */
178
+ export declare function warnOnHeldRunLockouts(db: Kysely<Database>, mode: OrchestratorMode): Promise<void>;
133
179
  export { resolveFullPolicyView } from '@kici-dev/engine/protocol/dashboard-write-operations';
134
180
  //# sourceMappingURL=dashboard-write-policy.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The canonical build context the orchestrator sends with a job, so the agent
3
+ * can freeze a provenance statement that says what a live mint would have said.
4
+ *
5
+ * A deferred attestation is signed BEFORE its identity token exists, so the
6
+ * agent used to build the frozen statement from facts it held locally — and
7
+ * those are not the facts the mint reads:
8
+ *
9
+ * - the job's checkout `ref` is the PR HEAD branch, while the token's `ref`
10
+ * claim is `execution_runs.ref`, the BASE branch;
11
+ * - `request.workflowRef` is the git ref used to CLONE a global workflow's
12
+ * repository, while the token's `workflow_ref` claim is
13
+ * `<workflow_name>@<sha>`. Those two are never equal;
14
+ * - `provenanceIssuer` is declared and read but written by no dispatch path,
15
+ * so every frozen statement recorded `builder.id` as `/orchestrator/unknown`.
16
+ *
17
+ * So a field-by-field cross-check would have failed on every legitimate
18
+ * deferred attestation — which is exactly why the verifier's deferred branch
19
+ * was written as a hash equality instead. Sending the mint's own view of the
20
+ * run makes the cross-check meaningful, and only then can it be switched on.
21
+ */
22
+ import type { Kysely } from 'kysely';
23
+ import { type ProvenanceContext } from '@kici-dev/engine';
24
+ import type { Database } from '../db/types.js';
25
+ /**
26
+ * Load the provenance context for a (run, job) from the same rows and the same
27
+ * derivations `buildIdTokenClaims` uses.
28
+ *
29
+ * Returns undefined only when the run row is absent. In particular this does
30
+ * NOT gate on a configured provenance issuer: the cross-check reads
31
+ * `repository` / `ref` / `sha` / `workflowRef` / `runId` / `jobId`, none of
32
+ * which depends on who signs the token. Only the statement's `builder.id`
33
+ * needs an issuer, and an unset one reproduces the `/orchestrator/unknown`
34
+ * builder the local guess has always recorded.
35
+ *
36
+ * Gating on the issuer here dropped the context for every orchestrator still
37
+ * on the deprecated Platform-relay mint, so its agent fell back to the local
38
+ * guess — `repository: 'unknown/unknown'` for a `file://` clone, and a bare
39
+ * workflow name where the claim is `<name>@<sha>` — and the capture check
40
+ * then refused every one of its deferred attestations.
41
+ */
42
+ export declare function loadProvenanceContext(db: Kysely<Database>, runId: string, jobId: string, opts: {
43
+ issuer: string | undefined;
44
+ orchestratorId: string;
45
+ }): Promise<ProvenanceContext | undefined>;
46
+ //# sourceMappingURL=dispatch-context.d.ts.map
@@ -0,0 +1,39 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { Database } from '../db/types.js';
3
+ import { DeferredAttestationRejectReason } from '../metrics/prometheus.js';
4
+ /** What the capture handler hands over, before anything is persisted. */
5
+ export interface DeferredCaptureRecord {
6
+ runId: string;
7
+ jobId: string;
8
+ subjectDigest: string;
9
+ statementHash: string;
10
+ dsseEnvelope: unknown;
11
+ }
12
+ /**
13
+ * Accepted, with the RECOMPUTED statement hash — never the agent-supplied one,
14
+ * even when they agree. The later mint binds the token to this value.
15
+ */
16
+ export type DeferredCaptureVerdict = {
17
+ ok: true;
18
+ statementHash: string;
19
+ } | {
20
+ ok: false;
21
+ reason: DeferredAttestationRejectReason;
22
+ detail: string;
23
+ };
24
+ /**
25
+ * Check a deferred capture against the orchestrator's own rows, fail closed.
26
+ *
27
+ * A rejection is a DROP: the job stays green and has no attestation.
28
+ * That is a degradation, whereas storing an unchecked statement re-introduces
29
+ * the forgery primitive — every gate that lets one through is the hole again.
30
+ * An agent too old to receive `provenanceContext` freezes the legacy statement
31
+ * shape and is dropped for the same reason, with the mismatched field named so
32
+ * the operator can see the upgrade is what fixes it.
33
+ */
34
+ export declare function checkDeferredCapture(deps: {
35
+ db: Kysely<Database>;
36
+ record: DeferredCaptureRecord;
37
+ orchestratorId: string;
38
+ }): Promise<DeferredCaptureVerdict>;
39
+ //# sourceMappingURL=verify-deferred-capture.d.ts.map
@@ -15,6 +15,15 @@ export interface GitHubAppConfig {
15
15
  /** GitHub App private key (PEM format) */
16
16
  privateKey: string;
17
17
  }
18
+ /**
19
+ * Create an Octokit instance authenticated as the GitHub App itself (a JWT
20
+ * signed with the App private key), not as one of its installations.
21
+ *
22
+ * App-level endpoints — `GET /app`, `GET /app/installations`, and the webhook
23
+ * delivery endpoints under `/app/hook/deliveries` — accept only this
24
+ * credential. Installation tokens are rejected there.
25
+ */
26
+ export declare function createAppOctokit(config: GitHubAppConfig): Octokit;
18
27
  /**
19
28
  * Credentials for GitHub operations requiring installation-level access.
20
29
  */