@kici-dev/orchestrator 0.1.26 → 0.2.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.
- package/dist/__test-helpers__/mock-db.d.ts +2 -0
- package/dist/agent/dispatcher.d.ts +52 -2
- package/dist/agent/host-roster-reaper.d.ts +1 -2
- package/dist/agent/host-roster.d.ts +34 -0
- package/dist/agent/ownership-refusal.d.ts +12 -0
- package/dist/agent/ownership-tracker.d.ts +64 -16
- package/dist/agent/token-store.d.ts +13 -0
- package/dist/agent-packaging/availability.d.ts +27 -0
- package/dist/agent-packaging/build-package.d.ts +16 -0
- package/dist/agent-packaging/download.d.ts +30 -0
- package/dist/agent-packaging/index.d.ts +9 -0
- package/dist/agent-packaging/install-closure.d.ts +30 -0
- package/dist/agent-packaging/node-binary.d.ts +26 -0
- package/dist/agent-packaging/refresh.d.ts +50 -0
- package/dist/agent-packaging/store.d.ts +15 -0
- package/dist/agent-packaging/upload.d.ts +24 -0
- package/dist/app-on-error.d.ts +29 -0
- package/dist/app.d.ts +69 -8
- package/dist/approvals/apply-decision.d.ts +1 -1
- package/dist/approvals/step-approval-bridge.d.ts +1 -1
- package/dist/artifacts/artifact-store.d.ts +233 -0
- package/dist/artifacts/failure-messages.d.ts +46 -0
- package/dist/audit/access-log.d.ts +18 -1
- package/dist/cache/dep-cache.d.ts +15 -0
- package/dist/cache/pending-inits.d.ts +4 -4
- package/dist/cache/user-cache.d.ts +37 -0
- package/dist/cancel/cancel-run.d.ts +10 -3
- package/dist/cancel/dashboard-cancel-handler.d.ts +19 -0
- package/dist/cli/api-client.d.ts +51 -5
- package/dist/cli/commands/agent-package.d.ts +34 -0
- package/dist/cli/commands/check-run.d.ts +26 -0
- package/dist/cli/commands/cluster-settings.d.ts +55 -0
- package/dist/cli/commands/context.d.ts +25 -0
- package/dist/cli/commands/dashboard-encryption-key.d.ts +3 -0
- package/dist/cli/commands/db-backup.d.ts +71 -0
- package/dist/cli/commands/debug-bundle.d.ts +1 -1
- package/dist/cli/commands/orchestrator-service/drain.d.ts +31 -0
- package/dist/cli/commands/orchestrator-service/index.d.ts +7 -4
- package/dist/cli/commands/orchestrator-service/install-env.d.ts +58 -0
- package/dist/cli/commands/secret.d.ts +45 -1
- package/dist/cli/commands/shared/admin-cli-access-log.d.ts +46 -0
- package/dist/cli/commands/shared/versioned-upgrade.d.ts +65 -2
- package/dist/cli/commands/signing-key.d.ts +3 -0
- package/dist/cli/commands/source.d.ts +12 -0
- package/dist/cli/commands/token.d.ts +9 -0
- package/dist/cli/commands/trust-policy.d.ts +48 -0
- package/dist/cli/commands/variable.d.ts +6 -6
- package/dist/cli/join.d.ts +1 -1
- package/dist/cli/service/index.d.ts +2 -2
- package/dist/cli/service/instance/manifest.d.ts +17 -0
- package/dist/cli/service/instance/resolve.d.ts +11 -0
- package/dist/cli/service/windows.d.ts +6 -0
- package/dist/cli/wizard/orchestrator-wizard.d.ts +39 -11
- package/dist/cli.js +10263 -3667
- package/dist/cluster/cluster-settings-reader.d.ts +78 -0
- package/dist/cluster/coordinator.d.ts +81 -9
- package/dist/cluster/join-client.d.ts +1 -1
- package/dist/cluster/leader-gated-scheduler.d.ts +50 -0
- package/dist/cluster/orphan-recovery.d.ts +14 -0
- package/dist/cluster/peer-client.d.ts +12 -2
- package/dist/cluster/peer-handler.d.ts +15 -4
- package/dist/cluster/peer-registry.d.ts +12 -0
- package/dist/cluster/verified-issuer-poller.d.ts +11 -0
- package/dist/cluster/verified-issuer.d.ts +63 -0
- package/dist/cold-store/tables/access-log.d.ts +1 -1
- package/dist/cold-store/tables/event-log.d.ts +1 -1
- package/dist/cold-store/tables/execution-jobs.d.ts +1 -1
- package/dist/cold-store/tables/execution-runs.d.ts +1 -1
- package/dist/cold-store/tables/execution-steps.d.ts +1 -1
- package/dist/cold-store/tables/secret-audit-log.d.ts +1 -1
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/resolver.d.ts +2 -1
- package/dist/config/schema.d.ts +8 -6
- package/dist/config/types.d.ts +3 -2
- package/dist/config.d.ts +372 -46
- package/dist/contexts/binding-store.d.ts +36 -0
- package/dist/contexts/context-store.d.ts +100 -0
- package/dist/contexts/glob-specificity.d.ts +30 -0
- package/dist/{environments → contexts}/held-runs.d.ts +64 -32
- package/dist/{environments → contexts}/index.d.ts +1 -1
- package/dist/contexts/protection/aggregate.d.ts +43 -0
- package/dist/contexts/protection/branch-gate.d.ts +5 -0
- package/dist/contexts/protection/concurrency-gate.d.ts +4 -0
- package/dist/{environments → contexts}/protection/pipeline.d.ts +3 -3
- package/dist/contexts/protection/reviewer-gate.d.ts +4 -0
- package/dist/{environments → contexts}/protection/satisfiability.d.ts +22 -22
- package/dist/contexts/protection/trust-gate.d.ts +4 -0
- package/dist/contexts/protection/wait-timer-gate.d.ts +4 -0
- package/dist/contexts/variable-store.d.ts +39 -0
- package/dist/cron/cron-scheduler.d.ts +24 -4
- package/dist/cron/cron-store.d.ts +21 -7
- package/dist/dashboard/attestation-filters.d.ts +94 -74
- package/dist/dashboard/handler.d.ts +63 -3
- package/dist/db/migration-test-harness.d.ts +41 -0
- package/dist/db/migrations/067_environments_to_contexts.d.ts +36 -0
- package/dist/db/migrations/068_request_idempotency.d.ts +17 -0
- package/dist/db/migrations/069_reroute_tunables.d.ts +21 -0
- package/dist/db/migrations/070_execution_runs_failure_class.d.ts +4 -0
- package/dist/db/migrations/071_batch_accumulation.d.ts +26 -0
- package/dist/db/migrations/072_dispatch_queue_run_id_index.d.ts +15 -0
- package/dist/db/migrations/073_org_settings_ingest_concurrency.d.ts +13 -0
- package/dist/db/migrations/074_normalize_zero_concurrency_limit.d.ts +13 -0
- package/dist/db/migrations/075_ingest_overflow_buffer.d.ts +16 -0
- package/dist/db/migrations/076_artifacts.d.ts +19 -0
- package/dist/db/migrations/077_backup_runs.d.ts +13 -0
- package/dist/db/migrations/078_org_settings_backup_staleness.d.ts +12 -0
- package/dist/db/migrations/079_org_settings_scaler_spawn_timeout.d.ts +14 -0
- package/dist/db/migrations/080_cluster_settings.d.ts +17 -0
- package/dist/db/migrations/081_org_settings_queue_timeout.d.ts +19 -0
- package/dist/db/migrations/082_host_s3_reachable.d.ts +16 -0
- package/dist/db/migrations/083_org_settings_artifact_caps.d.ts +18 -0
- package/dist/db/migrations/084_orchestrator_signing_keys.d.ts +20 -0
- package/dist/db/migrations/085_cluster_settings_reroute_flap_grace_ms.d.ts +12 -0
- package/dist/db/migrations/086_cluster_settings_max_fanout_hosts.d.ts +12 -0
- package/dist/db/migrations/087_cluster_settings_event_router_rate_limit.d.ts +12 -0
- package/dist/db/migrations/088_cluster_settings_cache_max_tarball_bytes.d.ts +12 -0
- package/dist/db/migrations/089_cluster_settings_cache_ttl_days.d.ts +12 -0
- package/dist/db/migrations/090_cluster_settings_concurrency_wait_timeout_ms.d.ts +12 -0
- package/dist/db/migrations/091_cluster_settings_agent_token_ttl_ms.d.ts +12 -0
- package/dist/db/migrations/092_cluster_settings_version.d.ts +16 -0
- package/dist/db/migrations/093_org_settings_sandbox_allowlist.d.ts +4 -0
- package/dist/db/migrations/094_dashboard_encryption_keys.d.ts +21 -0
- package/dist/db/migrations/095_dashboard_write_policy_tristate.d.ts +20 -0
- package/dist/db/migrations/096_multi_schedule_cron_last_fired.d.ts +4 -0
- package/dist/db/migrations/097_execution_runs_pr_number.d.ts +4 -0
- package/dist/db/migrations/098_execution_runs_customer_id.d.ts +4 -0
- package/dist/db/migrations/099_cluster_settings_dashboard_verified_issuer.d.ts +21 -0
- package/dist/db/migrations/100_held_runs_hold_type_vocabulary.d.ts +31 -0
- package/dist/db/migrations/101_contexts_hold_expiry_drop_default.d.ts +31 -0
- package/dist/db/migrations/102_dispatch_queue_agent_id.d.ts +20 -0
- package/dist/db/migrations/103_cluster_settings_ownership_db_check_timeout_ms.d.ts +20 -0
- package/dist/db/migrations/104_check_run_terminal_sent.d.ts +22 -0
- package/dist/db/migrations/105_org_trust_policy.d.ts +19 -0
- package/dist/db/migrations/106_cluster_settings_check_run_tracking_ttl_days.d.ts +18 -0
- package/dist/db/migrations/107_check_run_tracking_updated_at_index.d.ts +23 -0
- package/dist/db/repos/dashboard-encryption-keys-repo.d.ts +48 -0
- package/dist/db/repos/signing-keys-repo.d.ts +34 -0
- package/dist/db/types.d.ts +499 -66
- package/dist/deployment/deployment-identity.d.ts +87 -2
- package/dist/diagnostics/checks/backup.d.ts +18 -0
- package/dist/diagnostics/checks/index.d.ts +2 -1
- package/dist/diagnostics/fleet-collector.d.ts +1 -1
- package/dist/drain/drain-controller.d.ts +52 -0
- package/dist/events/batch-accumulator.d.ts +63 -0
- package/dist/events/circuit-breaker.d.ts +9 -3
- package/dist/events/event-emitter.d.ts +31 -0
- package/dist/events/event-retry-scanner.d.ts +12 -2
- package/dist/events/event-router.d.ts +29 -0
- package/dist/firecracker/host-network.d.ts +2 -0
- package/dist/firecracker/net-interfaces.d.ts +14 -0
- package/dist/github-app-name-refresher/github-app-name-refresher.d.ts +19 -9
- package/dist/index.d.ts +2 -0
- package/dist/index.js +625 -22
- package/dist/lockfile-cache.d.ts +4 -0
- package/dist/lockfile-validate.d.ts +16 -9
- package/dist/metrics/prometheus.d.ts +114 -15
- package/dist/oidc/aws-kms-signer.d.ts +37 -0
- package/dist/oidc/command-signer.d.ts +23 -0
- package/dist/oidc/db-signer.d.ts +48 -0
- package/dist/oidc/id-token-claims.d.ts +76 -0
- package/dist/oidc/jwt.d.ts +18 -0
- package/dist/oidc/local-dev-signer.d.ts +31 -0
- package/dist/oidc/local-mint.d.ts +77 -0
- package/dist/oidc/oidc-mint-registration.d.ts +65 -0
- package/dist/oidc/orchestrator-mint.d.ts +97 -0
- package/dist/oidc/orchestrator-signer-factory.d.ts +33 -0
- package/dist/oidc/reconcile-signing-key.d.ts +21 -0
- package/dist/oidc/signer.d.ts +25 -0
- package/dist/oidc/signing-key-status.d.ts +23 -0
- package/dist/orchestrator-core.d.ts +42 -2
- package/dist/pipeline/decorating-secret-resolver.d.ts +5 -4
- package/dist/pipeline/dispatch-matched-workflow.d.ts +44 -3
- package/dist/pipeline/inline-eval.d.ts +2 -2
- package/dist/pipeline/install-secrets-resolver.d.ts +25 -14
- package/dist/pipeline/{job-environments.d.ts → job-contexts.d.ts} +23 -23
- package/dist/pipeline/manual-schedule.d.ts +28 -14
- package/dist/pipeline/pending-workflow-context.d.ts +1 -1
- package/dist/pipeline/process-webhook.d.ts +57 -2
- package/dist/pipeline/processor.d.ts +49 -13
- package/dist/pipeline/request-idempotency.d.ts +28 -0
- package/dist/pipeline/rerun.d.ts +20 -13
- package/dist/pipeline/resolve-sandbox-grant.d.ts +28 -0
- package/dist/pipeline/resume-workflow.d.ts +1 -1
- package/dist/pipeline/route-or-dispatch-jobs.d.ts +71 -0
- package/dist/pipeline/sandbox-allowlist-reader.d.ts +33 -0
- package/dist/pipeline/test-pipeline.d.ts +2 -2
- package/dist/policy/dashboard-write-policy.d.ts +9 -4
- package/dist/provenance/backfill-run.d.ts +1 -0
- package/dist/provenance/trust-root.d.ts +20 -0
- package/dist/provider-registry.d.ts +8 -0
- package/dist/providers/generic/normalizer.d.ts +1 -1
- package/dist/providers/github/auth.d.ts +1 -1
- package/dist/providers/github/changed-files.d.ts +14 -6
- package/dist/providers/github/check-status-poster.d.ts +19 -3
- package/dist/providers/github/contributor-resolver.d.ts +1 -1
- package/dist/providers/github/lock-file.d.ts +1 -1
- package/dist/providers/github/normalizer.d.ts +1 -1
- package/dist/providers/github/repo-url.d.ts +1 -1
- package/dist/providers/local/index.d.ts +1 -0
- package/dist/providers/local/local-source-config.d.ts +1 -0
- package/dist/providers/local/lock-file-fetcher.d.ts +1 -1
- package/dist/providers/local/normalizer.d.ts +1 -1
- package/dist/providers/local/repo-url-builder.d.ts +1 -1
- package/dist/providers/universal-git/changed-files.d.ts +8 -7
- package/dist/providers/universal-git/clone-token.d.ts +1 -1
- package/dist/providers/universal-git/config.d.ts +4 -4
- package/dist/providers/universal-git/index.d.ts +5 -1
- package/dist/providers/universal-git/lock-file.d.ts +6 -1
- package/dist/providers/universal-git/normalizer.d.ts +1 -1
- package/dist/providers/universal-git/repo-url.d.ts +1 -1
- package/dist/queue/cleanup.d.ts +69 -8
- package/dist/queue/job-queue.d.ts +94 -14
- package/dist/queue/scheduled-job.d.ts +2 -2
- package/dist/registration/registration-index.d.ts +1 -1
- package/dist/reporting/agent-failure-category.d.ts +4 -1
- package/dist/reporting/check-run-reporter.d.ts +34 -14
- package/dist/reporting/check-run-tracking-store.d.ts +70 -8
- package/dist/reporting/execution-tracker.d.ts +173 -17
- package/dist/reporting/fs-log-storage.d.ts +31 -2
- package/dist/reporting/job-check-run-completion.d.ts +50 -0
- package/dist/reporting/log-chunk-sink.d.ts +47 -0
- package/dist/reporting/log-retention.d.ts +16 -0
- package/dist/reporting/log-storage.d.ts +64 -4
- package/dist/reporting/log-writer.d.ts +22 -7
- package/dist/reporting/peer-log-normalize.d.ts +14 -0
- package/dist/reporting/run-aggregator.d.ts +36 -36
- package/dist/reporting/s3-log-storage.d.ts +102 -8
- package/dist/routes/admin-auth.d.ts +90 -0
- package/dist/routes/admin-cluster-settings.d.ts +31 -0
- package/dist/routes/admin-contexts.d.ts +47 -0
- package/dist/routes/admin-events.d.ts +3 -0
- package/dist/routes/admin-orchestrator-drain.d.ts +35 -0
- package/dist/routes/admin-registrations.d.ts +2 -2
- package/dist/routes/admin-runs.d.ts +9 -0
- package/dist/routes/admin-sources.d.ts +7 -0
- package/dist/routes/admin-trust-policy.d.ts +49 -0
- package/dist/routes/admin.d.ts +15 -1
- package/dist/routes/github-webhook.d.ts +9 -3
- package/dist/routes/provenance-oidc.d.ts +38 -0
- package/dist/routes/verify-attestation.d.ts +21 -0
- package/dist/scaler/bare-metal-backend.d.ts +9 -2
- package/dist/scaler/config.d.ts +25 -4
- package/dist/scaler/container-backend.d.ts +9 -1
- package/dist/scaler/firecracker-backend.d.ts +29 -2
- package/dist/scaler/index.d.ts +1 -1
- package/dist/scaler/manager.d.ts +166 -8
- package/dist/scaler/pending-scale-sweeper.d.ts +35 -0
- package/dist/scaler/types.d.ts +75 -6
- package/dist/secrets/backend-registry.d.ts +32 -1
- package/dist/secrets/context-secret-resolver.d.ts +30 -0
- package/dist/secrets/dashboard-encryption-key.d.ts +48 -0
- package/dist/secrets/ephemeral-keys.d.ts +16 -0
- package/dist/secrets/index.d.ts +1 -1
- package/dist/secrets/pg-secret-store.d.ts +13 -3
- package/dist/secrets/rbac.d.ts +5 -1
- package/dist/secrets/routing-key-scope.d.ts +6 -0
- package/dist/secrets/scope-routing.d.ts +126 -0
- package/dist/secrets/secret-resolver.d.ts +19 -32
- package/dist/secrets/token-manager.d.ts +5 -1
- package/dist/security/comment-handler.d.ts +1 -1
- package/dist/security/contributor-cache.d.ts +9 -1
- package/dist/security/trust-policy-gate.d.ts +117 -0
- package/dist/security/trust-policy-store.d.ts +78 -0
- package/dist/server.js +44012 -32721
- package/dist/sources/source-manager.d.ts +24 -0
- package/dist/stale-detector/stale-run-detector.d.ts +8 -1
- package/dist/standalone.js +44106 -33690
- package/dist/storage/blob-routes.d.ts +16 -0
- package/dist/storage/filesystem.d.ts +17 -0
- package/dist/storage/key-discriminator.d.ts +20 -0
- package/dist/storage/s3.d.ts +2 -0
- package/dist/storage/types.d.ts +16 -0
- package/dist/webhook/dedup.d.ts +9 -1
- package/dist/webhook/event-log.d.ts +13 -2
- package/dist/webhook/generic-sources-listener.d.ts +4 -0
- package/dist/webhook/ingest-admission.d.ts +140 -0
- package/dist/webhook/ingest-overflow-buffer.d.ts +27 -0
- package/dist/webhook/ingest-overflow-replayer.d.ts +49 -0
- package/dist/webhook/ingest-overflow-types.d.ts +73 -0
- package/dist/webhook/loop-lag-source.d.ts +40 -0
- package/dist/webhook/observed-relay-guard.d.ts +40 -0
- package/dist/webhook/org-ingest-cap-reader.d.ts +24 -0
- package/dist/webhook/register-source-bundle.d.ts +3 -0
- package/dist/webhook/relay-reinject.d.ts +43 -0
- package/dist/worker/in-memory-job-queue.d.ts +8 -3
- package/dist/worker-core.d.ts +10 -0
- package/dist/ws/agent-api-registry.d.ts +20 -0
- package/dist/ws/agent-handler.d.ts +63 -7
- package/dist/ws/bringup-api.d.ts +99 -0
- package/dist/ws/{dashboard-env-handler.d.ts → dashboard-context-handler.d.ts} +43 -25
- package/dist/ws/dashboard-run-state-handler.d.ts +31 -0
- package/dist/ws/failure-messages.d.ts +29 -0
- package/dist/ws/oidc-token-relay.d.ts +15 -0
- package/dist/ws/ownership-gate.d.ts +22 -0
- package/dist/ws/platform-client.d.ts +96 -1
- package/dist/ws/replay-chunker.d.ts +37 -0
- package/installer-image-digests.json +3 -3
- package/package.json +22 -21
- package/sbom.spdx.json +1245 -1793
- package/dist/cli/commands/environment.d.ts +0 -25
- package/dist/environments/binding-store.d.ts +0 -36
- package/dist/environments/environment-store.d.ts +0 -87
- package/dist/environments/protection/aggregate.d.ts +0 -43
- package/dist/environments/protection/branch-gate.d.ts +0 -5
- package/dist/environments/protection/concurrency-gate.d.ts +0 -7
- package/dist/environments/protection/reviewer-gate.d.ts +0 -7
- package/dist/environments/protection/trust-gate.d.ts +0 -7
- package/dist/environments/protection/wait-timer-gate.d.ts +0 -7
- package/dist/environments/variable-store.d.ts +0 -39
- package/dist/routes/admin-environments.d.ts +0 -47
package/dist/index.js
CHANGED
|
@@ -6,10 +6,12 @@ import { createLogger, createS3Client, sha256 } from "@kici-dev/shared";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import { mkdtempSync, promises, writeFileSync } from "node:fs";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
|
-
import { createHmac, hkdfSync, randomBytes, randomUUID } from "node:crypto";
|
|
9
|
+
import { createHash, createHmac, hkdfSync, randomBytes, randomUUID } from "node:crypto";
|
|
10
10
|
import { chmod, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
11
11
|
import { Kysely, PostgresDialect, sql } from "kysely";
|
|
12
12
|
import pg from "pg";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { DEFAULT_APPROVAL_EXPIRY_HOURS, HoldScope, HoldType, TriggerSource, persistedHoldTypeSpellings } from "@kici-dev/engine";
|
|
13
15
|
import { platform, tmpdir } from "node:os";
|
|
14
16
|
import { createInterface } from "node:readline";
|
|
15
17
|
import { Octokit } from "@octokit/rest";
|
|
@@ -189,6 +191,9 @@ var S3CacheStorage = class {
|
|
|
189
191
|
Key: this.objectKey(key)
|
|
190
192
|
}), { expiresIn: PRESIGNED_URL_EXPIRY_SECONDS });
|
|
191
193
|
}
|
|
194
|
+
presignedGetTtlSeconds() {
|
|
195
|
+
return PRESIGNED_URL_EXPIRY_SECONDS;
|
|
196
|
+
}
|
|
192
197
|
async getUploadUrl(key) {
|
|
193
198
|
const objectKey = this.objectKey(key);
|
|
194
199
|
return getSignedUrl(this.presignClient, new PutObjectCommand({
|
|
@@ -242,6 +247,17 @@ var S3CacheStorage = class {
|
|
|
242
247
|
async getMetadata(key) {
|
|
243
248
|
return this.readMeta(key);
|
|
244
249
|
}
|
|
250
|
+
async getObjectSize(key) {
|
|
251
|
+
try {
|
|
252
|
+
return (await this.client.send(new HeadObjectCommand({
|
|
253
|
+
Bucket: this.bucket,
|
|
254
|
+
Key: this.objectKey(key)
|
|
255
|
+
}))).ContentLength ?? null;
|
|
256
|
+
} catch (err) {
|
|
257
|
+
if (this.isNotFoundError(err)) return null;
|
|
258
|
+
throw err;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
245
261
|
async copy(srcKey, destKey) {
|
|
246
262
|
const srcObj = this.objectKey(srcKey);
|
|
247
263
|
const destObj = this.objectKey(destKey);
|
|
@@ -429,6 +445,9 @@ var FilesystemCacheStorage = class {
|
|
|
429
445
|
}
|
|
430
446
|
return this.signedUrl("GET", key);
|
|
431
447
|
}
|
|
448
|
+
presignedGetTtlSeconds() {
|
|
449
|
+
return Math.floor(DEFAULT_SIGN_URL_TTL_MS / 1e3);
|
|
450
|
+
}
|
|
432
451
|
async getUploadUrl(key) {
|
|
433
452
|
return this.signedUrl("PUT", key);
|
|
434
453
|
}
|
|
@@ -442,21 +461,48 @@ var FilesystemCacheStorage = class {
|
|
|
442
461
|
lastAccessedAt: now
|
|
443
462
|
});
|
|
444
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* List cache keys under a prefix.
|
|
466
|
+
*
|
|
467
|
+
* The prefix is a **key prefix**, not necessarily a directory — `UserCache`'s
|
|
468
|
+
* `restoreKeys` fallback lists on `${prefix}${seg(restoreKey)}`, a partial
|
|
469
|
+
* object name. Two shapes, and both must work the way the S3 backend does:
|
|
470
|
+
*
|
|
471
|
+
* - trailing `/` — a directory. Walk it directly; this is the hot path.
|
|
472
|
+
* - no trailing `/` — a partial name. Walk the parent directory and filter by
|
|
473
|
+
* the prefix, since the prefix itself may name a file (or nothing at all).
|
|
474
|
+
*
|
|
475
|
+
* Reading the prefix as a directory in the second case is what made the
|
|
476
|
+
* `restoreKeys` fallback silently never hit here (`ENOENT` → `[]`) or throw
|
|
477
|
+
* (`ENOTDIR`, when a real object carried that exact name).
|
|
478
|
+
*/
|
|
445
479
|
async list(subPrefix) {
|
|
446
|
-
const
|
|
480
|
+
const isDirPrefix = subPrefix.endsWith("/");
|
|
481
|
+
const trimmed = subPrefix.replace(/\/$/, "");
|
|
482
|
+
const walkKey = isDirPrefix ? trimmed : trimmed.slice(0, trimmed.lastIndexOf("/") + 1);
|
|
483
|
+
const walkRoot = walkKey === "" ? this.basePath : this.resolvePath(walkKey.replace(/\/$/, ""));
|
|
447
484
|
let entries;
|
|
448
485
|
try {
|
|
449
|
-
entries = await this.walkDataFiles(
|
|
486
|
+
entries = await this.walkDataFiles(walkRoot, walkKey.replace(/\/$/, ""));
|
|
450
487
|
} catch (err) {
|
|
451
488
|
if (isNotFoundFsError(err)) return [];
|
|
452
489
|
throw err;
|
|
453
490
|
}
|
|
491
|
+
if (!isDirPrefix) entries = entries.filter((e) => e.key.startsWith(trimmed));
|
|
454
492
|
entries.sort((a, b) => b.mtime - a.mtime);
|
|
455
493
|
return entries.map((e) => e.key);
|
|
456
494
|
}
|
|
457
495
|
async getMetadata(key) {
|
|
458
496
|
return this.readMeta(key);
|
|
459
497
|
}
|
|
498
|
+
async getObjectSize(key) {
|
|
499
|
+
try {
|
|
500
|
+
return (await promises.stat(this.resolvePath(key))).size;
|
|
501
|
+
} catch (err) {
|
|
502
|
+
if (isNotFoundFsError(err)) return null;
|
|
503
|
+
throw err;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
460
506
|
async copy(srcKey, destKey) {
|
|
461
507
|
const srcPath = this.resolvePath(srcKey);
|
|
462
508
|
const destPath = this.resolvePath(destKey);
|
|
@@ -504,7 +550,9 @@ var FilesystemCacheStorage = class {
|
|
|
504
550
|
}
|
|
505
551
|
};
|
|
506
552
|
function isNotFoundFsError(err) {
|
|
507
|
-
|
|
553
|
+
if (typeof err !== "object" || err === null) return false;
|
|
554
|
+
const code = err.code;
|
|
555
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
508
556
|
}
|
|
509
557
|
/**
|
|
510
558
|
* Write a file atomically: write to a temp sibling, fsync, rename. Avoids
|
|
@@ -629,6 +677,9 @@ var SourceCache = class {
|
|
|
629
677
|
const logger$2 = createLogger({ prefix: "dep-cache" });
|
|
630
678
|
/** Default max tarball size: 500MB */
|
|
631
679
|
const DEFAULT_MAX_TARBALL_BYTES = 524288e3;
|
|
680
|
+
/** Default dependency-cache entry TTL: 30 days. */
|
|
681
|
+
const DEFAULT_CACHE_TTL_DAYS = 30;
|
|
682
|
+
const MS_PER_DAY = 864e5;
|
|
632
683
|
/** Build cache key for dependency tarball: deps/{platform}-{arch}/{lockfileHash}.tar.gz */
|
|
633
684
|
function depKey(lockfileHash, platform, arch) {
|
|
634
685
|
return `deps/${platform}-${arch}/${lockfileHash}.tar.gz`;
|
|
@@ -636,14 +687,29 @@ function depKey(lockfileHash, platform, arch) {
|
|
|
636
687
|
var DepCache = class {
|
|
637
688
|
storage;
|
|
638
689
|
maxTarballBytes;
|
|
690
|
+
cacheTtlDaysFallback;
|
|
691
|
+
clusterSettings;
|
|
639
692
|
constructor(options) {
|
|
640
693
|
this.storage = options.storage;
|
|
641
694
|
this.maxTarballBytes = options.maxTarballBytes ?? DEFAULT_MAX_TARBALL_BYTES;
|
|
695
|
+
this.cacheTtlDaysFallback = options.cacheTtlDaysFallback ?? DEFAULT_CACHE_TTL_DAYS;
|
|
696
|
+
this.clusterSettings = options.clusterSettings;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Resolve the live dependency-cache entry TTL as a per-operation override
|
|
700
|
+
* (ms) from `cluster_settings.cache_ttl_days`, falling back to the cluster
|
|
701
|
+
* default. `undefined` when no reader is wired, so the storage backend uses
|
|
702
|
+
* its own configured TTL. Passed to read/expiry operations so an operator's
|
|
703
|
+
* `cache_ttl_days` change takes effect on the next lookup.
|
|
704
|
+
*/
|
|
705
|
+
async resolveTtlMsOverride() {
|
|
706
|
+
if (!this.clusterSettings) return void 0;
|
|
707
|
+
return await this.clusterSettings.getNumber("cache_ttl_days", this.cacheTtlDaysFallback) * MS_PER_DAY;
|
|
642
708
|
}
|
|
643
709
|
/** Check if a dep tarball exists in cache. */
|
|
644
710
|
async has(lockfileHash, platform, arch) {
|
|
645
711
|
const key = depKey(lockfileHash, platform, arch);
|
|
646
|
-
const exists = await this.storage.has(key);
|
|
712
|
+
const exists = await this.storage.has(key, await this.resolveTtlMsOverride());
|
|
647
713
|
logger$2.debug(`has(${lockfileHash}): ${exists}`, {
|
|
648
714
|
platform,
|
|
649
715
|
arch
|
|
@@ -656,7 +722,7 @@ var DepCache = class {
|
|
|
656
722
|
*/
|
|
657
723
|
async getUrl(lockfileHash, platform, arch) {
|
|
658
724
|
const key = depKey(lockfileHash, platform, arch);
|
|
659
|
-
const url = await this.storage.getUrl(key);
|
|
725
|
+
const url = await this.storage.getUrl(key, await this.resolveTtlMsOverride());
|
|
660
726
|
if (url) {
|
|
661
727
|
await this.storage.touch(key);
|
|
662
728
|
logger$2.debug(`getUrl(${lockfileHash}): hit`, {
|
|
@@ -676,7 +742,7 @@ var DepCache = class {
|
|
|
676
742
|
*/
|
|
677
743
|
async getUrlAndHash(lockfileHash, platform, arch) {
|
|
678
744
|
const key = depKey(lockfileHash, platform, arch);
|
|
679
|
-
const url = await this.storage.getUrl(key);
|
|
745
|
+
const url = await this.storage.getUrl(key, await this.resolveTtlMsOverride());
|
|
680
746
|
if (!url) {
|
|
681
747
|
logger$2.debug(`getUrlAndHash(${lockfileHash}): miss`, {
|
|
682
748
|
platform,
|
|
@@ -709,7 +775,8 @@ var DepCache = class {
|
|
|
709
775
|
* Throws if tarball exceeds max size (per user decision).
|
|
710
776
|
*/
|
|
711
777
|
async store(lockfileHash, platform, arch, tarballData) {
|
|
712
|
-
|
|
778
|
+
const maxTarballBytes = this.clusterSettings ? await this.clusterSettings.getNumber("cache_max_tarball_bytes", this.maxTarballBytes) : this.maxTarballBytes;
|
|
779
|
+
if (tarballData.length > maxTarballBytes) throw new Error(`Dep tarball exceeds max size: ${tarballData.length} bytes > ${maxTarballBytes} bytes limit`);
|
|
713
780
|
const key = depKey(lockfileHash, platform, arch);
|
|
714
781
|
await this.storage.put(key, tarballData);
|
|
715
782
|
logger$2.info(`store: ${tarballData.length} bytes`, {
|
|
@@ -736,13 +803,26 @@ var DepCache = class {
|
|
|
736
803
|
return removed;
|
|
737
804
|
}
|
|
738
805
|
};
|
|
806
|
+
/**
|
|
807
|
+
* A collision-resistant discriminator for a user-supplied object-key segment.
|
|
808
|
+
*
|
|
809
|
+
* Hashes the **exact** name, before any sanitization. Sanitizing is many-to-one
|
|
810
|
+
* — `seg()` maps both `/` and `_` to `_`, so `a/b` and `a_b` collapse to one
|
|
811
|
+
* segment — and a case-insensitive backend folds `build` onto `Build`. Hashing
|
|
812
|
+
* the pre-sanitization name is what keeps each of those pairs two objects.
|
|
813
|
+
*/
|
|
814
|
+
function keyDiscriminator(exact) {
|
|
815
|
+
return createHash("sha256").update(exact, "utf8").digest("hex").slice(0, 32);
|
|
816
|
+
}
|
|
739
817
|
//#endregion
|
|
740
818
|
//#region src/cache/user-cache.ts
|
|
741
819
|
/**
|
|
742
820
|
* User-facing cache layer wrapping CacheStorage.
|
|
743
821
|
*
|
|
744
|
-
* Namespacing: `cache/<orgId>/<repoId>/<refScope>/<key>.tar.gz
|
|
745
|
-
*
|
|
822
|
+
* Namespacing: `cache/<orgId>/<repoId>/<refScope>/<key>-<discriminator>.tar.gz`.
|
|
823
|
+
* The discriminator is a hash of the exact cache key, so two keys differing
|
|
824
|
+
* only by case stay two objects even on a case-insensitive store. `refScope`
|
|
825
|
+
* is `shared` for trusted refs (org-shared, default-branch cache)
|
|
746
826
|
* or `iso/<runId>` for untrusted refs (per-run isolated scope). Restores from
|
|
747
827
|
* an untrusted ref read the shared scope as a fallback but writes can NEVER
|
|
748
828
|
* land in the shared scope — the GitHub Actions cache-isolation model. Keyed
|
|
@@ -775,6 +855,42 @@ const DEFAULT_USER_CACHE_QUOTA_BYTES = 5 * 1024 * 1024 * 1024;
|
|
|
775
855
|
const DEFAULT_USER_CACHE_TTL_MS = 10080 * 60 * 1e3;
|
|
776
856
|
/** Tarball suffix for committed cache entries. */
|
|
777
857
|
const TAR_SUFFIX = ".tar.gz";
|
|
858
|
+
/** Matches a stem ending in the fixed-width discriminator this module appends. */
|
|
859
|
+
const KEY_DISCRIMINATOR_TAIL = new RegExp(`^(.*)-([0-9a-f]{32})$`);
|
|
860
|
+
/**
|
|
861
|
+
* Recover the reported cache key from a stored object's stem.
|
|
862
|
+
*
|
|
863
|
+
* The value on this path is already the sanitized, lossy form, so this only
|
|
864
|
+
* needs to undo the discriminator the key format appends. A stem that itself
|
|
865
|
+
* ends in a discriminator-shaped tail loses it — that affects only the string
|
|
866
|
+
* reported back as `matchedKey`, never which object is served.
|
|
867
|
+
*/
|
|
868
|
+
function stripKeyDiscriminator(stem) {
|
|
869
|
+
return KEY_DISCRIMINATOR_TAIL.exec(stem)?.[1] ?? stem;
|
|
870
|
+
}
|
|
871
|
+
/** Device names NTFS reserves; a file cannot actually be created under one. */
|
|
872
|
+
const WINDOWS_RESERVED_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|$)/;
|
|
873
|
+
/** Keys that cannot have been part of a case collision in the old key format. */
|
|
874
|
+
const UNAMBIGUOUS_LEGACY_KEY = /^[a-z0-9_][a-z0-9._-]*$/;
|
|
875
|
+
/**
|
|
876
|
+
* Whether a cache key is safe to look up in the pre-discriminator key format.
|
|
877
|
+
*
|
|
878
|
+
* Every clause rules out a way the old format could have folded two distinct
|
|
879
|
+
* keys onto one object:
|
|
880
|
+
*
|
|
881
|
+
* - the character class is a subset of what `seg()` preserves, so a match
|
|
882
|
+
* implies the key survives sanitization unchanged (no many-to-one rewrite)
|
|
883
|
+
* and is pure ASCII, which rules out HFS+ Unicode normalisation;
|
|
884
|
+
* - requiring lowercase rules out case folding, the collision this change fixes;
|
|
885
|
+
* - a trailing `.` is stripped by NTFS, and a reserved device name cannot be
|
|
886
|
+
* created there at all.
|
|
887
|
+
*
|
|
888
|
+
* A key failing any clause simply gets no legacy fallback — it is re-fetched
|
|
889
|
+
* and re-saved under the discriminated key, which is always correct.
|
|
890
|
+
*/
|
|
891
|
+
function unambiguousLegacyKey(key) {
|
|
892
|
+
return UNAMBIGUOUS_LEGACY_KEY.test(key) && !key.endsWith(".") && !WINDOWS_RESERVED_NAME.test(key);
|
|
893
|
+
}
|
|
778
894
|
var UserCache = class {
|
|
779
895
|
storage;
|
|
780
896
|
/** Cluster-wide default quota (the `KICI_USER_CACHE_QUOTA_BYTES` value). */
|
|
@@ -854,9 +970,61 @@ var UserCache = class {
|
|
|
854
970
|
}
|
|
855
971
|
return [`${base}/shared/`];
|
|
856
972
|
}
|
|
973
|
+
/**
|
|
974
|
+
* Object key for a committed cache entry.
|
|
975
|
+
*
|
|
976
|
+
* The trailing discriminator is a hash of the EXACT cache key, and it is what
|
|
977
|
+
* keeps `build` and `Build` apart on a case-insensitive namespace — the
|
|
978
|
+
* filesystem backend on a macOS or Windows host, where the two would
|
|
979
|
+
* otherwise resolve to one object and a restore could return the other key's
|
|
980
|
+
* tarball.
|
|
981
|
+
*
|
|
982
|
+
* It is deliberately a SUFFIX: `restoreByPrefix` matches `restoreKeys` by
|
|
983
|
+
* string prefix, so appending leaves those semantics untouched. The only
|
|
984
|
+
* parse that has to know about it is the `matchedKey` slice.
|
|
985
|
+
*/
|
|
857
986
|
finalKey(prefix, key) {
|
|
987
|
+
return `${prefix}${this.seg(key)}-${keyDiscriminator(key)}${TAR_SUFFIX}`;
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* The pre-discriminator object key.
|
|
991
|
+
*
|
|
992
|
+
* @deprecated Read-only compatibility path for entries written before the key
|
|
993
|
+
* carried a discriminator. Nothing writes this format; it is removed at the
|
|
994
|
+
* next major, by which point every such entry has aged out of the cache TTL.
|
|
995
|
+
*/
|
|
996
|
+
legacyFinalKey(prefix, key) {
|
|
858
997
|
return `${prefix}${this.seg(key)}${TAR_SUFFIX}`;
|
|
859
998
|
}
|
|
999
|
+
/**
|
|
1000
|
+
* Restore an entry still stored in the pre-discriminator key format.
|
|
1001
|
+
*
|
|
1002
|
+
* Two gates, and BOTH are load-bearing — dropping either re-creates the
|
|
1003
|
+
* wrong-cache-hit this change fixes:
|
|
1004
|
+
*
|
|
1005
|
+
* 1. `unambiguousLegacyKey` statically rules out any key that could have
|
|
1006
|
+
* collided with a case variant in the old format.
|
|
1007
|
+
* 2. The listed name must match byte-exactly. A case-insensitive backend
|
|
1008
|
+
* resolves `getUrl('build')` to an object created as `Build`, but its
|
|
1009
|
+
* *listing* reports the real created name — so comparing against the
|
|
1010
|
+
* listing is what detects that the object is not really ours.
|
|
1011
|
+
*
|
|
1012
|
+
* @deprecated Removed at the next major; see `legacyFinalKey`.
|
|
1013
|
+
*/
|
|
1014
|
+
async restoreLegacyExact(ref, prefix, ttlMs) {
|
|
1015
|
+
if (!unambiguousLegacyKey(ref.key)) return null;
|
|
1016
|
+
const legacyKey = this.legacyFinalKey(prefix, ref.key);
|
|
1017
|
+
if (!(await this.storage.list(prefix)).includes(legacyKey)) return null;
|
|
1018
|
+
const url = await this.storage.getUrl(legacyKey, ttlMs);
|
|
1019
|
+
if (!url) return null;
|
|
1020
|
+
await this.storage.touch(legacyKey);
|
|
1021
|
+
return {
|
|
1022
|
+
hit: true,
|
|
1023
|
+
matchedKey: ref.key,
|
|
1024
|
+
downloadUrl: url,
|
|
1025
|
+
tarHash: await this.readHash(legacyKey)
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
860
1028
|
/** Restore: try the exact key across read prefixes, then restoreKeys prefix scan (newest wins). */
|
|
861
1029
|
async restore(ref) {
|
|
862
1030
|
const { ttlMs } = await this.resolveLimits(ref.org);
|
|
@@ -879,6 +1047,8 @@ var UserCache = class {
|
|
|
879
1047
|
tarHash: await this.readHash(key)
|
|
880
1048
|
};
|
|
881
1049
|
}
|
|
1050
|
+
const legacy = await this.restoreLegacyExact(ref, prefix, ttlMs);
|
|
1051
|
+
if (legacy) return legacy;
|
|
882
1052
|
}
|
|
883
1053
|
return null;
|
|
884
1054
|
}
|
|
@@ -893,7 +1063,7 @@ var UserCache = class {
|
|
|
893
1063
|
await this.storage.touch(winner);
|
|
894
1064
|
return {
|
|
895
1065
|
hit: true,
|
|
896
|
-
matchedKey: winner.slice(prefix.length, -7),
|
|
1066
|
+
matchedKey: stripKeyDiscriminator(winner.slice(prefix.length, -7)),
|
|
897
1067
|
downloadUrl: url,
|
|
898
1068
|
tarHash: await this.readHash(winner)
|
|
899
1069
|
};
|
|
@@ -1328,7 +1498,9 @@ var JoinTokenManager = class {
|
|
|
1328
1498
|
role
|
|
1329
1499
|
};
|
|
1330
1500
|
const secret = randomBytes(32);
|
|
1331
|
-
const
|
|
1501
|
+
const routingB64 = Buffer.from(JSON.stringify(routing)).toString("base64url");
|
|
1502
|
+
const secretHex = secret.toString("hex");
|
|
1503
|
+
const token = `${TOKEN_PREFIX}.${routingB64}.${secretHex}`;
|
|
1332
1504
|
const { validationHash } = deriveKeys(secret);
|
|
1333
1505
|
await this.deps.db.insertInto("join_tokens").values({
|
|
1334
1506
|
id: randomUUID(),
|
|
@@ -1452,6 +1624,404 @@ function deriveKeys(secret) {
|
|
|
1452
1624
|
validationHash
|
|
1453
1625
|
};
|
|
1454
1626
|
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Reason a run was held in the security queue. Persisted verbatim in
|
|
1629
|
+
* `held_runs.reason` and switched on by `buildSecurityHoldSummary`.
|
|
1630
|
+
*/
|
|
1631
|
+
const SecurityHoldReason = z.enum([
|
|
1632
|
+
"workflow_modification",
|
|
1633
|
+
"unknown_contributor",
|
|
1634
|
+
"fork_pr",
|
|
1635
|
+
"context_trust"
|
|
1636
|
+
]);
|
|
1637
|
+
/** Manages held run lifecycle (pending -> approved/rejected/expired). */
|
|
1638
|
+
var HeldRunStore = class {
|
|
1639
|
+
db;
|
|
1640
|
+
constructor(db) {
|
|
1641
|
+
this.db = db;
|
|
1642
|
+
}
|
|
1643
|
+
/** Create a new held run with pending status. */
|
|
1644
|
+
async create(orgId, data) {
|
|
1645
|
+
return this.db.insertInto("held_runs").values({
|
|
1646
|
+
org_id: orgId,
|
|
1647
|
+
run_id: data.runId,
|
|
1648
|
+
job_id: data.jobId,
|
|
1649
|
+
context_id: data.contextId,
|
|
1650
|
+
hold_type: data.holdType,
|
|
1651
|
+
queue_type: data.queueType ?? "context",
|
|
1652
|
+
reason: data.reason,
|
|
1653
|
+
expires_at: data.expiresAt
|
|
1654
|
+
}).returningAll().executeTakeFirstOrThrow();
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Create a generalized approval hold (workflow/job/step scope, explicit or
|
|
1658
|
+
* context trigger) carrying a normalized `ApprovalRequirement`. Returns
|
|
1659
|
+
* the created row.
|
|
1660
|
+
*/
|
|
1661
|
+
async createHold(orgId, data) {
|
|
1662
|
+
return this.db.insertInto("held_runs").values({
|
|
1663
|
+
org_id: orgId,
|
|
1664
|
+
run_id: data.runId,
|
|
1665
|
+
job_id: data.jobId,
|
|
1666
|
+
context_id: data.contextId ?? null,
|
|
1667
|
+
hold_type: data.holdType ?? HoldType.enum.reviewer,
|
|
1668
|
+
queue_type: data.queueType ?? "context",
|
|
1669
|
+
reason: data.requirement.reason,
|
|
1670
|
+
expires_at: new Date(data.requirement.expiresAt),
|
|
1671
|
+
hold_scope: data.scope,
|
|
1672
|
+
step_index: data.stepIndex ?? null,
|
|
1673
|
+
trigger_source: data.triggerSource,
|
|
1674
|
+
approval_requirement: data.requirement,
|
|
1675
|
+
...data.payload !== void 0 && { payload: JSON.stringify(data.payload) }
|
|
1676
|
+
}).returningAll().executeTakeFirstOrThrow();
|
|
1677
|
+
}
|
|
1678
|
+
/** INSERT one decision row using the given executor (root or transaction). */
|
|
1679
|
+
insertDecisionRow(exec, heldRunId, data) {
|
|
1680
|
+
const clausesSatisfied = data.clausesSatisfied != null ? JSON.stringify(data.clausesSatisfied) : null;
|
|
1681
|
+
return exec.insertInto("held_run_approvals").values({
|
|
1682
|
+
held_run_id: heldRunId,
|
|
1683
|
+
approver_user_id: data.approverSub,
|
|
1684
|
+
decision: data.decision,
|
|
1685
|
+
clauses_satisfied: clausesSatisfied
|
|
1686
|
+
}).returningAll().executeTakeFirstOrThrow();
|
|
1687
|
+
}
|
|
1688
|
+
/** Flip a pending hold to 'approved' using the given executor. Undefined if not pending. */
|
|
1689
|
+
flipToApproved(exec, orgId, heldRunId) {
|
|
1690
|
+
return exec.updateTable("held_runs").set({
|
|
1691
|
+
status: "approved",
|
|
1692
|
+
resolved_at: sql`now()`
|
|
1693
|
+
}).where("id", "=", heldRunId).where("org_id", "=", orgId).where("status", "=", "pending").returningAll().executeTakeFirst();
|
|
1694
|
+
}
|
|
1695
|
+
/** Flip a pending hold to 'rejected' using the given executor. Undefined if not pending. */
|
|
1696
|
+
flipToRejected(exec, orgId, heldRunId, reason) {
|
|
1697
|
+
const set = {
|
|
1698
|
+
status: "rejected",
|
|
1699
|
+
resolved_at: sql`now()`
|
|
1700
|
+
};
|
|
1701
|
+
if (reason !== void 0) set.reason = reason;
|
|
1702
|
+
return exec.updateTable("held_runs").set(set).where("id", "=", heldRunId).where("org_id", "=", orgId).where("status", "=", "pending").returningAll().executeTakeFirst();
|
|
1703
|
+
}
|
|
1704
|
+
/** Map a released held_runs row to the resume ReleaseSignal. */
|
|
1705
|
+
toReleaseSignal(row) {
|
|
1706
|
+
return {
|
|
1707
|
+
holdId: row.id,
|
|
1708
|
+
runId: row.run_id,
|
|
1709
|
+
jobId: row.job_id,
|
|
1710
|
+
scope: row.hold_scope ?? HoldScope.enum.job,
|
|
1711
|
+
stepIndex: row.step_index,
|
|
1712
|
+
triggerSource: row.trigger_source ?? TriggerSource.enum.context
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
/** Record one approve/reject decision against a hold. */
|
|
1716
|
+
async recordDecision(heldRunId, data) {
|
|
1717
|
+
return this.insertDecisionRow(this.db, heldRunId, data);
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Atomically record an approve decision and release the (now-satisfied) hold.
|
|
1721
|
+
* The INSERT into `held_run_approvals` and the `held_runs` → approved UPDATE
|
|
1722
|
+
* run in a single transaction, so a crash between them cannot strand the hold
|
|
1723
|
+
* `pending` with a recorded approve. Throws if the hold is not found or no
|
|
1724
|
+
* longer pending (the whole transaction rolls back).
|
|
1725
|
+
*/
|
|
1726
|
+
async recordAndRelease(orgId, heldRunId, data) {
|
|
1727
|
+
return this.db.transaction().execute(async (tx) => {
|
|
1728
|
+
await this.insertDecisionRow(tx, heldRunId, data);
|
|
1729
|
+
const row = await this.flipToApproved(tx, orgId, heldRunId);
|
|
1730
|
+
if (!row) throw new Error(`Held run '${heldRunId}' not found or not pending`);
|
|
1731
|
+
return this.toReleaseSignal(row);
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Atomically record a reject decision and reject the hold. The INSERT and the
|
|
1736
|
+
* `held_runs` → rejected UPDATE run in a single transaction, so a crash
|
|
1737
|
+
* between them cannot strand the hold `pending` with a recorded reject (which
|
|
1738
|
+
* would poison `evaluate()` forever). Throws if the hold is not found or no
|
|
1739
|
+
* longer pending (the whole transaction rolls back).
|
|
1740
|
+
*/
|
|
1741
|
+
async recordAndReject(orgId, heldRunId, data, reason) {
|
|
1742
|
+
return this.db.transaction().execute(async (tx) => {
|
|
1743
|
+
await this.insertDecisionRow(tx, heldRunId, data);
|
|
1744
|
+
const row = await this.flipToRejected(tx, orgId, heldRunId, reason);
|
|
1745
|
+
if (!row) throw new Error(`Held run '${heldRunId}' not found or not pending`);
|
|
1746
|
+
return row;
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
/** List the recorded decisions for a hold, oldest first. */
|
|
1750
|
+
async listDecisions(heldRunId) {
|
|
1751
|
+
return this.db.selectFrom("held_run_approvals").selectAll().where("held_run_id", "=", heldRunId).orderBy("created_at", "asc").execute();
|
|
1752
|
+
}
|
|
1753
|
+
/** Get a single held run by id (org-scoped). Returns null if absent. */
|
|
1754
|
+
async getById(orgId, heldRunId) {
|
|
1755
|
+
return await this.db.selectFrom("held_runs").selectAll().where("id", "=", heldRunId).where("org_id", "=", orgId).executeTakeFirst() ?? null;
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Release a hold whose approval requirement is satisfied. Flips the row to
|
|
1759
|
+
* 'approved' and returns a `ReleaseSignal` describing how the caller must
|
|
1760
|
+
* resume the element (re-dispatch for job/workflow, agent notification for
|
|
1761
|
+
* step). Throws if the hold is not found or not pending. Approver attribution
|
|
1762
|
+
* lives in `held_run_approvals`, not on the row.
|
|
1763
|
+
*/
|
|
1764
|
+
async release(orgId, heldRunId) {
|
|
1765
|
+
const row = await this.flipToApproved(this.db, orgId, heldRunId);
|
|
1766
|
+
if (!row) throw new Error(`Held run '${heldRunId}' not found or not pending`);
|
|
1767
|
+
return this.toReleaseSignal(row);
|
|
1768
|
+
}
|
|
1769
|
+
/** Approve a pending held run. Throws if not found or not pending. */
|
|
1770
|
+
async approve(orgId, heldRunId, approvedBy) {
|
|
1771
|
+
const row = await this.db.updateTable("held_runs").set({
|
|
1772
|
+
status: "approved",
|
|
1773
|
+
approved_by: approvedBy,
|
|
1774
|
+
resolved_at: sql`now()`
|
|
1775
|
+
}).where("id", "=", heldRunId).where("org_id", "=", orgId).where("status", "=", "pending").returningAll().executeTakeFirst();
|
|
1776
|
+
if (!row) throw new Error(`Held run '${heldRunId}' not found or not pending`);
|
|
1777
|
+
return row;
|
|
1778
|
+
}
|
|
1779
|
+
/** Reject a pending held run. Throws if not found or not pending. */
|
|
1780
|
+
async reject(orgId, heldRunId, reason) {
|
|
1781
|
+
const row = await this.flipToRejected(this.db, orgId, heldRunId, reason);
|
|
1782
|
+
if (!row) throw new Error(`Held run '${heldRunId}' not found or not pending`);
|
|
1783
|
+
return row;
|
|
1784
|
+
}
|
|
1785
|
+
/** List pending held runs for an org, ordered by creation time. */
|
|
1786
|
+
async listPending(orgId) {
|
|
1787
|
+
return this.db.selectFrom("held_runs").selectAll().where("org_id", "=", orgId).where("status", "=", "pending").orderBy("created_at", "asc").execute();
|
|
1788
|
+
}
|
|
1789
|
+
/** List held runs for an org with optional filters. */
|
|
1790
|
+
async listAll(orgId, options) {
|
|
1791
|
+
let query = this.db.selectFrom("held_runs").selectAll().where("org_id", "=", orgId).orderBy("created_at", "desc");
|
|
1792
|
+
if (options?.status) query = query.where("status", "=", options.status);
|
|
1793
|
+
if (options?.limit) query = query.limit(options.limit);
|
|
1794
|
+
return query.execute();
|
|
1795
|
+
}
|
|
1796
|
+
/** List held runs for an org filtered by queue type with optional filters. */
|
|
1797
|
+
async listByQueueType(orgId, queueType, options) {
|
|
1798
|
+
let query = this.db.selectFrom("held_runs").selectAll().where("org_id", "=", orgId).where("queue_type", "=", queueType).orderBy("created_at", "desc");
|
|
1799
|
+
if (options?.status) query = query.where("status", "=", options.status);
|
|
1800
|
+
if (options?.limit) query = query.limit(options.limit);
|
|
1801
|
+
return query.execute();
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* List pending `security` holds whose run belongs to a specific PR (and repo).
|
|
1805
|
+
*
|
|
1806
|
+
* Joins `execution_runs` on `run_id` and filters by `repo_identifier` +
|
|
1807
|
+
* `pr_number` so `/kici approve|reject` only affects the commented PR's holds
|
|
1808
|
+
* rather than every pending security hold in the org. A run with a NULL
|
|
1809
|
+
* `pr_number` (non-PR run, or a legacy run predating the column) never matches
|
|
1810
|
+
* the equality predicate, so such holds are never released here — fail-closed.
|
|
1811
|
+
*/
|
|
1812
|
+
async listPendingSecurityHoldsForPr(orgId, repoIdentifier, prNumber) {
|
|
1813
|
+
return this.db.selectFrom("held_runs").innerJoin("execution_runs", "execution_runs.run_id", "held_runs.run_id").selectAll("held_runs").where("held_runs.org_id", "=", orgId).where("held_runs.queue_type", "=", "security").where("held_runs.status", "=", "pending").where("execution_runs.repo_identifier", "=", repoIdentifier).where("execution_runs.pr_number", "=", prNumber).orderBy("held_runs.created_at", "desc").execute();
|
|
1814
|
+
}
|
|
1815
|
+
/**
|
|
1816
|
+
* Approve a pending held run, enforcing queue_type boundary.
|
|
1817
|
+
* Prevents context approvals from approving security holds and vice versa.
|
|
1818
|
+
* Throws if not found, not pending, or queue_type mismatch.
|
|
1819
|
+
*/
|
|
1820
|
+
async approveByQueueType(orgId, heldRunId, approvedBy, queueType) {
|
|
1821
|
+
const row = await this.db.updateTable("held_runs").set({
|
|
1822
|
+
status: "approved",
|
|
1823
|
+
approved_by: approvedBy,
|
|
1824
|
+
resolved_at: sql`now()`
|
|
1825
|
+
}).where("id", "=", heldRunId).where("org_id", "=", orgId).where("status", "=", "pending").where("queue_type", "=", queueType).returningAll().executeTakeFirst();
|
|
1826
|
+
if (!row) throw new Error(`Held run '${heldRunId}' not found, not pending, or queue type mismatch (expected '${queueType}')`);
|
|
1827
|
+
return row;
|
|
1828
|
+
}
|
|
1829
|
+
/** Get a held run by run ID and job ID. Returns null if not found. */
|
|
1830
|
+
async getByRunAndJob(orgId, runId, jobId) {
|
|
1831
|
+
return await this.db.selectFrom("held_runs").selectAll().where("org_id", "=", orgId).where("run_id", "=", runId).where("job_id", "=", jobId).executeTakeFirst() ?? null;
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* List pending holds past their `expires_at`. Called by the stale detector
|
|
1835
|
+
* BEFORE `expireOverdue()` so it can route each overdue hold by scope (step
|
|
1836
|
+
* holds notify the waiting agent; job/workflow holds fail the run).
|
|
1837
|
+
*/
|
|
1838
|
+
async listOverdue() {
|
|
1839
|
+
return this.db.selectFrom("held_runs").selectAll().where("status", "=", "pending").where("expires_at", "<", sql`now()`).execute();
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Release overdue workflow timer holds. The install-gate wait action pauses
|
|
1843
|
+
* the workflow as a held run; on timer expiry it must RESUME (not fail like
|
|
1844
|
+
* a reviewer-hold expiry). Flips each overdue pending `hold_type` timer,
|
|
1845
|
+
* `hold_scope='workflow'` row to `released` and returns a `ReleaseSignal`
|
|
1846
|
+
* per row so the caller can resume the workflow. Runs BEFORE
|
|
1847
|
+
* `expireOverdue()` so these rows leave the pending pool before the
|
|
1848
|
+
* expire-and-fail sweep sees them.
|
|
1849
|
+
*
|
|
1850
|
+
* The filter matches every persisted spelling of the timer hold type, so a
|
|
1851
|
+
* row an un-upgraded orchestrator wrote as `wait_timer` still resumes rather
|
|
1852
|
+
* than falling through to the expire-and-fail sweep. `hold_scope` is what
|
|
1853
|
+
* keeps job-scoped dispatch-gate timer holds out of this sweep.
|
|
1854
|
+
*/
|
|
1855
|
+
async releaseDueWaitHolds() {
|
|
1856
|
+
return (await this.db.updateTable("held_runs").set({
|
|
1857
|
+
status: "released",
|
|
1858
|
+
resolved_at: sql`now()`
|
|
1859
|
+
}).where("status", "=", "pending").where("hold_type", "in", persistedHoldTypeSpellings(HoldType.enum.timer)).where("hold_scope", "=", HoldScope.enum.workflow).where("expires_at", "<", sql`now()`).returningAll().execute()).map((row) => ({
|
|
1860
|
+
holdId: row.id,
|
|
1861
|
+
runId: row.run_id,
|
|
1862
|
+
jobId: row.job_id,
|
|
1863
|
+
scope: row.hold_scope ?? HoldScope.enum.workflow,
|
|
1864
|
+
stepIndex: row.step_index,
|
|
1865
|
+
triggerSource: row.trigger_source ?? TriggerSource.enum.context
|
|
1866
|
+
}));
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Expire overdue pending runs. Called by the stale detector.
|
|
1870
|
+
* Sets status to 'expired' and resolved_at to now() for all
|
|
1871
|
+
* pending runs past their expires_at.
|
|
1872
|
+
* Returns the number of expired runs.
|
|
1873
|
+
*/
|
|
1874
|
+
async expireOverdue() {
|
|
1875
|
+
const result = await this.db.updateTable("held_runs").set({
|
|
1876
|
+
status: "expired",
|
|
1877
|
+
resolved_at: sql`now()`
|
|
1878
|
+
}).where("status", "=", "pending").where("expires_at", "<", sql`now()`).execute();
|
|
1879
|
+
const updateResult = Array.isArray(result) ? result[0] : result;
|
|
1880
|
+
return Number(updateResult?.numUpdatedRows ?? 0n);
|
|
1881
|
+
}
|
|
1882
|
+
};
|
|
1883
|
+
/**
|
|
1884
|
+
* Build a HeldRunStore backed by its own connection pool to the given
|
|
1885
|
+
* orchestrator database URL. Mirrors `createPeerCredentialStoreFromUrl` /
|
|
1886
|
+
* `createJoinTokenManagerFromUrl`; consumed by E2E tests that exercise the
|
|
1887
|
+
* PR-scoped hold selection against the real deployed orchestrator DB.
|
|
1888
|
+
*/
|
|
1889
|
+
function createHeldRunStoreFromUrl(databaseUrl, opts) {
|
|
1890
|
+
const db = new Kysely({ dialect: new PostgresDialect({ pool: new pg.Pool({
|
|
1891
|
+
connectionString: databaseUrl,
|
|
1892
|
+
max: opts?.maxConnections ?? 3
|
|
1893
|
+
}) }) });
|
|
1894
|
+
return {
|
|
1895
|
+
store: new HeldRunStore(db),
|
|
1896
|
+
dispose: async () => {
|
|
1897
|
+
await db.destroy();
|
|
1898
|
+
}
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
//#endregion
|
|
1902
|
+
//#region src/security/trust-policy-store.ts
|
|
1903
|
+
/**
|
|
1904
|
+
* Cache of the Platform-owned org trust policy.
|
|
1905
|
+
*
|
|
1906
|
+
* The Platform is the authority for a Platform-attached org and pushes the
|
|
1907
|
+
* policy on `trust_policy.update`; this store persists it so the policy survives
|
|
1908
|
+
* a restart instead of being unknown until the next push. In independent mode
|
|
1909
|
+
* there is no Platform, so the operator writes the same row via
|
|
1910
|
+
* `kici-admin trust-policy set` and `source` records which wrote it.
|
|
1911
|
+
*/
|
|
1912
|
+
/** Who wrote the cached policy row. */
|
|
1913
|
+
const TrustPolicySource = z.enum(["platform", "local"]);
|
|
1914
|
+
/**
|
|
1915
|
+
* The strictest documented defaults. Used to fill gaps on a local merge; the
|
|
1916
|
+
* fail-closed policy an unknown org resolves to lives in `trust-policy-gate.ts`.
|
|
1917
|
+
*/
|
|
1918
|
+
const DEFAULT_TRUST_POLICY = {
|
|
1919
|
+
forkPolicy: "hold",
|
|
1920
|
+
unknownContributorPolicy: "hold",
|
|
1921
|
+
workflowChangePolicy: "hold",
|
|
1922
|
+
approvalExpiryHours: DEFAULT_APPROVAL_EXPIRY_HOURS
|
|
1923
|
+
};
|
|
1924
|
+
/**
|
|
1925
|
+
* Namespace for the per-org advisory lock taken by `upsertLocal`, so the key
|
|
1926
|
+
* cannot collide with another feature's advisory lock in the same database.
|
|
1927
|
+
*/
|
|
1928
|
+
const UPSERT_LOCK_NAMESPACE = "org-trust-policy-upsert";
|
|
1929
|
+
var TrustPolicyStore = class {
|
|
1930
|
+
db;
|
|
1931
|
+
constructor(db) {
|
|
1932
|
+
this.db = db;
|
|
1933
|
+
}
|
|
1934
|
+
/** `executor` lets a caller read inside its own transaction. */
|
|
1935
|
+
async get(orgId, executor = this.db) {
|
|
1936
|
+
const row = await executor.selectFrom("org_trust_policy").selectAll().where("customer_id", "=", orgId).executeTakeFirst();
|
|
1937
|
+
if (!row) return null;
|
|
1938
|
+
return {
|
|
1939
|
+
forkPolicy: row.fork_policy,
|
|
1940
|
+
unknownContributorPolicy: row.unknown_contributor_policy,
|
|
1941
|
+
workflowChangePolicy: row.workflow_change_policy,
|
|
1942
|
+
approvalExpiryHours: Number(row.approval_expiry_hours),
|
|
1943
|
+
source: row.source,
|
|
1944
|
+
updatedAt: row.updated_at
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Needs no lock, unlike `upsertLocal`: a push carries every field, so there
|
|
1949
|
+
* is no read-merge to lose and concurrent pushes correctly settle on the
|
|
1950
|
+
* last one. The two writers also never overlap — `upsertLocal` is reachable
|
|
1951
|
+
* only in independent mode, where no Platform pushes.
|
|
1952
|
+
*/
|
|
1953
|
+
async upsertFromPlatform(orgId, policy) {
|
|
1954
|
+
await this.write(orgId, policy, TrustPolicySource.enum.platform);
|
|
1955
|
+
}
|
|
1956
|
+
/**
|
|
1957
|
+
* Write an operator-supplied policy. Only reachable in independent mode — the
|
|
1958
|
+
* admin route refuses on a Platform-attached orchestrator, because the next
|
|
1959
|
+
* push would clobber the write.
|
|
1960
|
+
*
|
|
1961
|
+
* The read-merge-write is serialised per org by a transaction-scoped
|
|
1962
|
+
* advisory lock. The transaction alone would NOT give that: under READ
|
|
1963
|
+
* COMMITTED two concurrent PATCHes both read the pre-existing row, and the
|
|
1964
|
+
* second `ON CONFLICT DO UPDATE` then overwrites every column from its own
|
|
1965
|
+
* stale merge — silently dropping the first operator's change (a tightened
|
|
1966
|
+
* `unknownContributorPolicy` reverting to whatever the second caller last
|
|
1967
|
+
* saw). The lock releases on commit or rollback, so there is no unlock to
|
|
1968
|
+
* leak.
|
|
1969
|
+
*
|
|
1970
|
+
* `onWrite` receives the same transaction and the merged result, so an audit
|
|
1971
|
+
* row written there commits or rolls back with the policy itself — a
|
|
1972
|
+
* loosened `forkPolicy` can never land unattributed.
|
|
1973
|
+
*
|
|
1974
|
+
* Returns the merged policy so the caller does not need a second read (which
|
|
1975
|
+
* would be outside the transaction and could observe a later write).
|
|
1976
|
+
*/
|
|
1977
|
+
async upsertLocal(orgId, patch, onWrite) {
|
|
1978
|
+
return await this.db.transaction().execute(async (trx) => {
|
|
1979
|
+
await sql`SELECT pg_advisory_xact_lock(hashtext(${`${UPSERT_LOCK_NAMESPACE}|${orgId}`}))`.execute(trx);
|
|
1980
|
+
const existing = await this.get(orgId, trx);
|
|
1981
|
+
const merged = {
|
|
1982
|
+
forkPolicy: patch.forkPolicy ?? existing?.forkPolicy ?? DEFAULT_TRUST_POLICY.forkPolicy,
|
|
1983
|
+
unknownContributorPolicy: patch.unknownContributorPolicy ?? existing?.unknownContributorPolicy ?? DEFAULT_TRUST_POLICY.unknownContributorPolicy,
|
|
1984
|
+
workflowChangePolicy: patch.workflowChangePolicy ?? existing?.workflowChangePolicy ?? DEFAULT_TRUST_POLICY.workflowChangePolicy,
|
|
1985
|
+
approvalExpiryHours: patch.approvalExpiryHours ?? existing?.approvalExpiryHours ?? DEFAULT_TRUST_POLICY.approvalExpiryHours
|
|
1986
|
+
};
|
|
1987
|
+
await this.write(orgId, merged, TrustPolicySource.enum.local, trx);
|
|
1988
|
+
await onWrite?.(trx, merged);
|
|
1989
|
+
return merged;
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
async write(orgId, policy, source, executor = this.db) {
|
|
1993
|
+
const columns = {
|
|
1994
|
+
fork_policy: policy.forkPolicy,
|
|
1995
|
+
unknown_contributor_policy: policy.unknownContributorPolicy,
|
|
1996
|
+
workflow_change_policy: policy.workflowChangePolicy,
|
|
1997
|
+
approval_expiry_hours: policy.approvalExpiryHours,
|
|
1998
|
+
source,
|
|
1999
|
+
updated_at: sql`now()`
|
|
2000
|
+
};
|
|
2001
|
+
await executor.insertInto("org_trust_policy").values({
|
|
2002
|
+
customer_id: orgId,
|
|
2003
|
+
...columns
|
|
2004
|
+
}).onConflict((oc) => oc.column("customer_id").doUpdateSet(columns)).execute();
|
|
2005
|
+
}
|
|
2006
|
+
};
|
|
2007
|
+
/**
|
|
2008
|
+
* Build a TrustPolicyStore backed by its own connection pool to the given
|
|
2009
|
+
* orchestrator database URL. Mirrors `createHeldRunStoreFromUrl`; consumed by
|
|
2010
|
+
* E2E tests that assert the pushed policy is persisted in the real deployed
|
|
2011
|
+
* orchestrator DB rather than held in process memory.
|
|
2012
|
+
*/
|
|
2013
|
+
function createTrustPolicyStoreFromUrl(databaseUrl, opts) {
|
|
2014
|
+
const db = new Kysely({ dialect: new PostgresDialect({ pool: new pg.Pool({
|
|
2015
|
+
connectionString: databaseUrl,
|
|
2016
|
+
max: opts?.maxConnections ?? 3
|
|
2017
|
+
}) }) });
|
|
2018
|
+
return {
|
|
2019
|
+
store: new TrustPolicyStore(db),
|
|
2020
|
+
dispose: async () => {
|
|
2021
|
+
await db.destroy();
|
|
2022
|
+
}
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
1455
2025
|
//#endregion
|
|
1456
2026
|
//#region src/cli/api-client.ts
|
|
1457
2027
|
/**
|
|
@@ -1558,8 +2128,17 @@ var AdminApiClient = class {
|
|
|
1558
2128
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
1559
2129
|
fs.writeFileSync(outPath, buf);
|
|
1560
2130
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
2131
|
+
/**
|
|
2132
|
+
* List secret scopes for an org.
|
|
2133
|
+
*
|
|
2134
|
+
* @param allBackends - Aggregate every registered backend and return scopes
|
|
2135
|
+
* in qualified `<backend>:<path>` form. Default (false) returns the bare,
|
|
2136
|
+
* pg-only listing; that default flips at v1.0.0.
|
|
2137
|
+
*/
|
|
2138
|
+
async listScopes(orgId, allBackends = false) {
|
|
2139
|
+
const params = new URLSearchParams({ orgId });
|
|
2140
|
+
if (allBackends) params.set("allBackends", "true");
|
|
2141
|
+
return this.request("GET", `/api/v1/admin/secrets/scopes?${params}`);
|
|
1563
2142
|
}
|
|
1564
2143
|
async listKeys(orgId, scope) {
|
|
1565
2144
|
const params = new URLSearchParams({
|
|
@@ -1574,20 +2153,35 @@ var AdminApiClient = class {
|
|
|
1574
2153
|
async deleteSecret(orgId, scope, key) {
|
|
1575
2154
|
return this.request("DELETE", `/api/v1/admin/secrets/${encodeURIComponent(orgId)}/${encodeURIComponent(scope)}/${encodeURIComponent(key)}`);
|
|
1576
2155
|
}
|
|
1577
|
-
|
|
2156
|
+
/**
|
|
2157
|
+
* Create (or upsert) a deployment context. `allowLocalExecution` lets
|
|
2158
|
+
* CLI-initiated / local runs resolve secrets through this context.
|
|
2159
|
+
*/
|
|
2160
|
+
async createContext(data) {
|
|
2161
|
+
return this.request("POST", `/api/v1/admin/contexts`, data);
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Bind a scope pattern to a context so its scoped secrets resolve at dispatch.
|
|
2165
|
+
* `hostPattern` defaults to `**` (every host) server-side.
|
|
2166
|
+
*/
|
|
2167
|
+
async bindContext(data) {
|
|
2168
|
+
const { name, ...body } = data;
|
|
2169
|
+
return this.request("POST", `/api/v1/admin/contexts/${encodeURIComponent(name)}/bind`, body);
|
|
2170
|
+
}
|
|
2171
|
+
async listVariables(orgId, context) {
|
|
1578
2172
|
const params = new URLSearchParams({ orgId });
|
|
1579
|
-
return this.request("GET", `/api/v1/admin/
|
|
2173
|
+
return this.request("GET", `/api/v1/admin/contexts/${encodeURIComponent(context)}/variables?${params}`);
|
|
1580
2174
|
}
|
|
1581
|
-
async setVariable(orgId,
|
|
2175
|
+
async setVariable(orgId, context, key, value, locked) {
|
|
1582
2176
|
const params = new URLSearchParams({ orgId });
|
|
1583
|
-
return this.request("PUT", `/api/v1/admin/
|
|
2177
|
+
return this.request("PUT", `/api/v1/admin/contexts/${encodeURIComponent(context)}/variables/${encodeURIComponent(key)}?${params}`, {
|
|
1584
2178
|
value,
|
|
1585
2179
|
locked
|
|
1586
2180
|
});
|
|
1587
2181
|
}
|
|
1588
|
-
async deleteVariable(orgId,
|
|
2182
|
+
async deleteVariable(orgId, context, key) {
|
|
1589
2183
|
const params = new URLSearchParams({ orgId });
|
|
1590
|
-
return this.request("DELETE", `/api/v1/admin/
|
|
2184
|
+
return this.request("DELETE", `/api/v1/admin/contexts/${encodeURIComponent(context)}/variables/${encodeURIComponent(key)}?${params}`);
|
|
1591
2185
|
}
|
|
1592
2186
|
async rotateKey() {
|
|
1593
2187
|
return this.request("POST", "/api/v1/admin/rotate-key");
|
|
@@ -1702,6 +2296,14 @@ var AdminApiClient = class {
|
|
|
1702
2296
|
async configReload(opts) {
|
|
1703
2297
|
return this.request("POST", "/admin/config/reload", opts);
|
|
1704
2298
|
}
|
|
2299
|
+
/** Flip the coordinator drain flag on ('drain') or off ('resume'). */
|
|
2300
|
+
async drain(action) {
|
|
2301
|
+
return this.request("POST", "/api/v1/admin/orchestrator/drain", { action });
|
|
2302
|
+
}
|
|
2303
|
+
/** Report the coordinator's current drain state without changing it. */
|
|
2304
|
+
async drainStatus() {
|
|
2305
|
+
return this.request("GET", "/api/v1/admin/orchestrator/drain");
|
|
2306
|
+
}
|
|
1705
2307
|
async createApiKey(opts) {
|
|
1706
2308
|
return this.request("POST", "/api/v1/api-keys", opts);
|
|
1707
2309
|
}
|
|
@@ -2187,7 +2789,8 @@ async function resolveWebhookUrl(client) {
|
|
|
2187
2789
|
*/
|
|
2188
2790
|
async function captureCode(opts, deps, manifestJson, state, redirectUrl, createUrl, loopback) {
|
|
2189
2791
|
if (opts.noBrowser) {
|
|
2190
|
-
const
|
|
2792
|
+
const m = Buffer.from(manifestJson, "utf-8").toString("base64url");
|
|
2793
|
+
const url = `${STATIC_CALLBACK_URL}#m=${m}&state=${encodeURIComponent(state)}&createUrl=${encodeURIComponent(createUrl)}`;
|
|
2191
2794
|
console.log("Open this URL in a browser to create your App:");
|
|
2192
2795
|
console.log(` ${url}`);
|
|
2193
2796
|
console.log("After clicking \"Create\", copy the setup code shown on the page.");
|
|
@@ -2272,6 +2875,6 @@ async function runGithubManifestSetup(opts, client, deps = realManifestSetupDeps
|
|
|
2272
2875
|
}
|
|
2273
2876
|
}
|
|
2274
2877
|
//#endregion
|
|
2275
|
-
export { AdminApiClient, DEFAULT_USER_CACHE_QUOTA_BYTES, DEFAULT_USER_CACHE_TTL_MS, DepCache, JoinTokenManager, PeerAuthCoordinator, PeerCredentialStore, S3CacheStorage, SourceCache, UserCache, createCacheStorage, createJoinTokenManagerFromUrl, createPeerCredentialStoreFromUrl, runGithubManifestSetup };
|
|
2878
|
+
export { AdminApiClient, DEFAULT_TRUST_POLICY, DEFAULT_USER_CACHE_QUOTA_BYTES, DEFAULT_USER_CACHE_TTL_MS, DepCache, HeldRunStore, JoinTokenManager, PeerAuthCoordinator, PeerCredentialStore, S3CacheStorage, SecurityHoldReason, SourceCache, TrustPolicySource, TrustPolicyStore, UserCache, createCacheStorage, createHeldRunStoreFromUrl, createJoinTokenManagerFromUrl, createPeerCredentialStoreFromUrl, createTrustPolicyStoreFromUrl, runGithubManifestSetup };
|
|
2276
2879
|
|
|
2277
2880
|
//# sourceMappingURL=index.js.map
|