@kici-dev/orchestrator 0.1.16 → 0.1.18
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/agent/dispatcher.d.ts +27 -1
- package/dist/agent/host-roster-reaper.d.ts +42 -0
- package/dist/agent/host-roster.d.ts +110 -0
- package/dist/agent/registry.d.ts +65 -2
- package/dist/app.d.ts +3 -0
- package/dist/approvals/apply-decision.d.ts +11 -1
- package/dist/cache/pending-inits.d.ts +5 -0
- package/dist/cli/api-client.d.ts +11 -0
- package/dist/cli/commands/host.d.ts +13 -0
- package/dist/cli/commands/local-hook.d.ts +15 -0
- package/dist/cli/commands/local-trigger.d.ts +24 -0
- package/dist/cli/commands/remote-source.d.ts +17 -0
- package/dist/cli/commands/shared/versioned-upgrade.d.ts +37 -0
- package/dist/cli/kici-admin.d.ts +13 -0
- package/dist/cli/service/compose.d.ts +2 -1
- package/dist/cli/service/index.d.ts +2 -2
- package/dist/cli/service/instance/manifest.d.ts +12 -0
- package/dist/cli/service/instance/resolve.d.ts +12 -2
- package/dist/cli/service/launchd.d.ts +4 -1
- package/dist/cli/service/platform-detect.d.ts +5 -0
- package/dist/cli/service/systemd.d.ts +2 -1
- package/dist/cli/service/types.d.ts +37 -0
- package/dist/cli/service/windows.d.ts +2 -1
- package/dist/cli.js +1340 -198
- package/dist/cluster/coordinator.d.ts +5 -1
- package/dist/config/schema.d.ts +4 -0
- package/dist/config/types.d.ts +9 -0
- package/dist/config.d.ts +6 -4
- package/dist/dashboard/handler.d.ts +26 -2
- package/dist/dashboard/needs-edges.d.ts +13 -0
- package/dist/db/migrations/035_pending_workflow_contexts.d.ts +11 -0
- package/dist/db/migrations/036_attestations.d.ts +15 -0
- package/dist/db/migrations/037_generic_sources_provider_type_local.d.ts +19 -0
- package/dist/db/migrations/038_remote_sources.d.ts +14 -0
- package/dist/db/migrations/039_host_roster.d.ts +19 -0
- package/dist/db/migrations/040_runsonall_pin.d.ts +4 -0
- package/dist/db/migrations/041_wave_gated.d.ts +4 -0
- package/dist/db/migrations/042_dispatch_queue_patterns.d.ts +4 -0
- package/dist/db/types.d.ts +144 -10
- package/dist/diagnostics/fleet-collector.d.ts +1 -1
- package/dist/entry-helpers.d.ts +7 -29
- package/dist/environments/held-runs.d.ts +33 -0
- package/dist/index.js +1 -0
- package/dist/lockfile-redos-guard.d.ts +19 -0
- package/dist/metrics/prometheus.d.ts +17 -2
- package/dist/metrics/scheduled-jobs.d.ts +2 -2
- package/dist/orchestrator-core.d.ts +94 -1
- package/dist/pipeline/dispatch-matched-workflow.d.ts +60 -2
- package/dist/pipeline/install-secrets-resolver.d.ts +32 -5
- package/dist/pipeline/needs-scheduler.d.ts +12 -10
- package/dist/pipeline/pending-workflow-context.d.ts +44 -0
- package/dist/pipeline/processor.d.ts +31 -4
- package/dist/pipeline/remote-source-store.d.ts +21 -0
- package/dist/pipeline/resume-workflow.d.ts +26 -0
- package/dist/pipeline/wave-scheduler.d.ts +60 -0
- package/dist/providers/local/index.d.ts +33 -0
- package/dist/providers/local/local-source-config.d.ts +17 -0
- package/dist/providers/{internal → local}/lock-file-fetcher.d.ts +6 -6
- package/dist/providers/{internal → local}/normalizer.d.ts +28 -28
- package/dist/providers/{internal → local}/repo-url-builder.d.ts +6 -6
- package/dist/queue/job-queue.d.ts +61 -2
- package/dist/reporting/execution-tracker.d.ts +61 -0
- package/dist/routes/admin-events.d.ts +3 -4
- package/dist/routes/uploads.d.ts +36 -26
- package/dist/server.js +53755 -50888
- package/dist/sources/build-platform-sources.d.ts +4 -2
- package/dist/stale-detector/stale-run-detector.d.ts +14 -1
- package/dist/standalone.js +17234 -15725
- package/dist/webhook/generic-sources-listener.d.ts +4 -0
- package/dist/webhook/generic-sources.d.ts +37 -11
- package/dist/webhook/register-source-bundle.d.ts +14 -4
- package/dist/ws/agent-handler.d.ts +17 -0
- package/dist/ws/dashboard-dispatch-guard.d.ts +21 -0
- package/dist/ws/dashboard-env-handler.d.ts +13 -1
- package/dist/ws/oidc-token-relay.d.ts +59 -0
- package/dist/ws/platform-client.d.ts +30 -1
- package/dist/ws/test-relay-handlers.d.ts +112 -0
- package/installer-image-digests.json +3 -3
- package/package.json +22 -22
- package/sbom.spdx.json +3228 -6633
- package/dist/providers/internal/index.d.ts +0 -32
- package/dist/routes/test-trigger.d.ts +0 -41
- package/dist/ws/observer-handler.d.ts +0 -42
package/dist/cli.js
CHANGED
|
@@ -3,9 +3,9 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
5
5
|
import path, { join, resolve } from "node:path";
|
|
6
|
-
import { Command } from "commander";
|
|
7
6
|
import * as fs$1 from "node:fs";
|
|
8
|
-
import fs, { closeSync, createReadStream, createWriteStream, existsSync, openSync, promises, readFileSync, statSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
|
|
7
|
+
import fs, { chmodSync, closeSync, createReadStream, createWriteStream, existsSync, openSync, promises, readFileSync, realpathSync, statSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
|
|
8
|
+
import { Command } from "commander";
|
|
9
9
|
import { BaseColdStore, ChunkLru, addLogsToArchive, chunkObjectKey, clearDispatchQueueDirect, computeChunkId, computeMigrationsHash, createDb, createDbRole, createEnvironmentTemplateDirect, createLogger, createPool, createReadOnlyDbUser, decodeChunk, deleteEnvironmentDirect, dropAndCreateDatabase, emitKiciEventDirect, encodeKeySegment, ensureDatabase, formatBytes, formatUptime, isSchemaCurrent, listEnvironmentsDirect, listExecutionRunsDirect, listQueueDirect, listRegistrationsDirect, maskDatabaseUrl, parseDatabaseUrl, parseManifest, prunePeerCredentialsDirect, purgeScopedSecretsDirect, purgeSecretBackendsDirect, purgeStaleExecutionDirect, purgeStaleSourcesDirect, registerWorkflowManualDirect, resetRaftStateDirect, seedEnvironmentBindingDirect, seedEnvironmentDirect, setEnvironmentPolicyDirect, setEnvironmentSecretDirect, sha256, showEnvironmentDirect, showExecutionRunDirect, showQueueEntryDirect, showRegistrationDirect, storeMigrationContentHash, tablePrefix, toErrorMessage } from "@kici-dev/shared";
|
|
10
10
|
import crypto, { createDecipheriv, createHash, hkdfSync, randomBytes, randomUUID } from "node:crypto";
|
|
11
11
|
import { createInterface } from "node:readline";
|
|
@@ -19,14 +19,14 @@ import { Migrator } from "kysely/migration";
|
|
|
19
19
|
import "jsonpath-plus";
|
|
20
20
|
import { execFile, execFileSync, execSync, spawn } from "node:child_process";
|
|
21
21
|
import { promisify } from "node:util";
|
|
22
|
-
import
|
|
22
|
+
import { ZipArchive } from "archiver";
|
|
23
23
|
import { confirm, input, password, select } from "@inquirer/prompts";
|
|
24
24
|
import JSZip from "jszip";
|
|
25
25
|
import { pipeline } from "node:stream/promises";
|
|
26
26
|
import "pg";
|
|
27
|
+
import { KICI_AGENT_ENV_PREFIX, KNOWN_ROLES, ScalerBackendType, ScalerEventType, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, WS_MAX_PAYLOAD_BYTES, accessLogWarmSqlCase, getAccessLogColdDays, getSecretAuditLogColdDays, matcherSatisfiedBy, minAccessLogWarmDays, minSecretAuditLogWarmDays, scalerAgentLabels, secretAuditLogWarmSqlCase } from "@kici-dev/engine";
|
|
27
28
|
import { DASHBOARD_WRITE_OPERATIONS, DASHBOARD_WRITE_OPERATIONS_BY_NAME, DashboardWriteCategory, DashboardWriteSensitivity } from "@kici-dev/engine/protocol/dashboard-write-operations";
|
|
28
29
|
import { CLUSTER_NAME_FORMAT_MESSAGE } from "@kici-dev/engine/protocol/cluster-name";
|
|
29
|
-
import { KICI_AGENT_ENV_PREFIX, KNOWN_ROLES, ScalerBackendType, ScalerEventType, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, WS_MAX_PAYLOAD_BYTES, accessLogWarmSqlCase, getAccessLogColdDays, getSecretAuditLogColdDays, minAccessLogWarmDays, minSecretAuditLogWarmDays, scalerAgentLabels, secretAuditLogWarmSqlCase } from "@kici-dev/engine";
|
|
30
30
|
import http from "node:http";
|
|
31
31
|
import { PassThrough } from "node:stream";
|
|
32
32
|
import Docker from "dockerode";
|
|
@@ -52,6 +52,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
52
52
|
* error formatting, and URL construction.
|
|
53
53
|
*/
|
|
54
54
|
var AdminApiClient = class {
|
|
55
|
+
baseUrl;
|
|
56
|
+
token;
|
|
55
57
|
constructor(baseUrl, token) {
|
|
56
58
|
this.baseUrl = baseUrl;
|
|
57
59
|
this.token = token;
|
|
@@ -977,6 +979,8 @@ var init_schema = __esmMin((() => {
|
|
|
977
979
|
}).optional(),
|
|
978
980
|
agentAuth: z.enum(["token", "none"]).optional(),
|
|
979
981
|
agentTokenTtlMs: z.coerce.number().optional(),
|
|
982
|
+
rosterGraceMs: z.coerce.number().optional(),
|
|
983
|
+
rosterTtlMs: z.coerce.number().optional(),
|
|
980
984
|
queue: z.object({
|
|
981
985
|
maxDepth: z.coerce.number().optional(),
|
|
982
986
|
timeoutMs: z.coerce.number().optional(),
|
|
@@ -1053,6 +1057,8 @@ var init_schema = __esmMin((() => {
|
|
|
1053
1057
|
}).optional(),
|
|
1054
1058
|
agentAuth: z.enum(["token", "none"]).default("token"),
|
|
1055
1059
|
agentTokenTtlMs: z.coerce.number().default(36e5),
|
|
1060
|
+
rosterGraceMs: z.coerce.number().default(3e5),
|
|
1061
|
+
rosterTtlMs: z.coerce.number().default(18e5),
|
|
1056
1062
|
queueMaxDepth: z.coerce.number().default(1e3),
|
|
1057
1063
|
queueTimeoutMs: z.coerce.number().default(36e5),
|
|
1058
1064
|
/**
|
|
@@ -1548,8 +1554,8 @@ var init_client = __esmMin((() => {
|
|
|
1548
1554
|
//#region src/db/migrations/001_initial.ts
|
|
1549
1555
|
init_client();
|
|
1550
1556
|
var _001_initial_exports = /* @__PURE__ */ __exportAll({
|
|
1551
|
-
down: () => down$
|
|
1552
|
-
up: () => up$
|
|
1557
|
+
down: () => down$41,
|
|
1558
|
+
up: () => up$41
|
|
1553
1559
|
});
|
|
1554
1560
|
/**
|
|
1555
1561
|
* Squashed initial migration -- creates the complete Orchestrator database schema.
|
|
@@ -2241,7 +2247,7 @@ const DDL_STATEMENTS = [
|
|
|
2241
2247
|
`ALTER TABLE ONLY public.held_runs
|
|
2242
2248
|
ADD CONSTRAINT held_runs_environment_id_fkey FOREIGN KEY (environment_id) REFERENCES public.environments(id);`
|
|
2243
2249
|
];
|
|
2244
|
-
async function up$
|
|
2250
|
+
async function up$41(db) {
|
|
2245
2251
|
for (const stmt of DDL_STATEMENTS) await sql.raw(stmt).execute(db);
|
|
2246
2252
|
await sql`
|
|
2247
2253
|
INSERT INTO cluster_meta (key, value)
|
|
@@ -2263,7 +2269,7 @@ async function up$33(db) {
|
|
|
2263
2269
|
* Rollback drops everything created above. Uses CASCADE on table drops to cut
|
|
2264
2270
|
* through the FK graph without relying on exact topological order.
|
|
2265
2271
|
*/
|
|
2266
|
-
async function down$
|
|
2272
|
+
async function down$41(db) {
|
|
2267
2273
|
for (const [trig, tbl] of [["source_secrets_change_trigger", "scoped_secrets"], ["sources_change_trigger", "sources"]]) await sql.raw(`DROP TRIGGER IF EXISTS ${trig} ON public.${tbl}`).execute(db);
|
|
2268
2274
|
for (const table of [
|
|
2269
2275
|
"workflow_registrations",
|
|
@@ -2310,8 +2316,8 @@ async function down$33(db) {
|
|
|
2310
2316
|
//#endregion
|
|
2311
2317
|
//#region src/db/migrations/002_config_versions_key_version.ts
|
|
2312
2318
|
var _002_config_versions_key_version_exports = /* @__PURE__ */ __exportAll({
|
|
2313
|
-
down: () => down$
|
|
2314
|
-
up: () => up$
|
|
2319
|
+
down: () => down$40,
|
|
2320
|
+
up: () => up$40
|
|
2315
2321
|
});
|
|
2316
2322
|
/**
|
|
2317
2323
|
* Add key_version column to config_versions so that sensitive-field encryption
|
|
@@ -2325,13 +2331,13 @@ var _002_config_versions_key_version_exports = /* @__PURE__ */ __exportAll({
|
|
|
2325
2331
|
* No index is needed: rotation does a full-table scan; reads are by
|
|
2326
2332
|
* `version` primary key and never filter on `key_version`.
|
|
2327
2333
|
*/
|
|
2328
|
-
async function up$
|
|
2334
|
+
async function up$40(db) {
|
|
2329
2335
|
await sql`
|
|
2330
2336
|
ALTER TABLE public.config_versions
|
|
2331
2337
|
ADD COLUMN key_version integer NOT NULL DEFAULT 1
|
|
2332
2338
|
`.execute(db);
|
|
2333
2339
|
}
|
|
2334
|
-
async function down$
|
|
2340
|
+
async function down$40(db) {
|
|
2335
2341
|
await sql`
|
|
2336
2342
|
ALTER TABLE public.config_versions
|
|
2337
2343
|
DROP COLUMN key_version
|
|
@@ -2340,8 +2346,8 @@ async function down$32(db) {
|
|
|
2340
2346
|
//#endregion
|
|
2341
2347
|
//#region src/db/migrations/003_access_log.ts
|
|
2342
2348
|
var _003_access_log_exports = /* @__PURE__ */ __exportAll({
|
|
2343
|
-
down: () => down$
|
|
2344
|
-
up: () => up$
|
|
2349
|
+
down: () => down$39,
|
|
2350
|
+
up: () => up$39
|
|
2345
2351
|
});
|
|
2346
2352
|
/**
|
|
2347
2353
|
* Access log: one row per read or orchestrator-admin mutation attributable
|
|
@@ -2365,7 +2371,7 @@ var _003_access_log_exports = /* @__PURE__ */ __exportAll({
|
|
|
2365
2371
|
* Retention is TTL-based via expires_at; packages/orchestrator/src/queue/
|
|
2366
2372
|
* cleanup.ts picks up the prune pass.
|
|
2367
2373
|
*/
|
|
2368
|
-
async function up$
|
|
2374
|
+
async function up$39(db) {
|
|
2369
2375
|
await sql`
|
|
2370
2376
|
CREATE TABLE public.access_log (
|
|
2371
2377
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
@@ -2403,28 +2409,28 @@ async function up$31(db) {
|
|
|
2403
2409
|
ON public.access_log (actor_type, actor_id, created_at DESC)
|
|
2404
2410
|
`.execute(db);
|
|
2405
2411
|
}
|
|
2406
|
-
async function down$
|
|
2412
|
+
async function down$39(db) {
|
|
2407
2413
|
await sql`DROP TABLE IF EXISTS public.access_log`.execute(db);
|
|
2408
2414
|
}
|
|
2409
2415
|
//#endregion
|
|
2410
2416
|
//#region src/db/migrations/004_rename_bundle_to_source.ts
|
|
2411
2417
|
var _004_rename_bundle_to_source_exports = /* @__PURE__ */ __exportAll({
|
|
2412
|
-
down: () => down$
|
|
2413
|
-
up: () => up$
|
|
2418
|
+
down: () => down$38,
|
|
2419
|
+
up: () => up$38
|
|
2414
2420
|
});
|
|
2415
|
-
async function up$
|
|
2421
|
+
async function up$38(db) {
|
|
2416
2422
|
await db.schema.alterTable("dispatch_queue").renameColumn("bundle_url", "source_tar_url").execute();
|
|
2417
2423
|
await db.schema.alterTable("dispatch_queue").renameColumn("bundle_hash", "source_tar_hash").execute();
|
|
2418
2424
|
}
|
|
2419
|
-
async function down$
|
|
2425
|
+
async function down$38(db) {
|
|
2420
2426
|
await db.schema.alterTable("dispatch_queue").renameColumn("source_tar_url", "bundle_url").execute();
|
|
2421
2427
|
await db.schema.alterTable("dispatch_queue").renameColumn("source_tar_hash", "bundle_hash").execute();
|
|
2422
2428
|
}
|
|
2423
2429
|
//#endregion
|
|
2424
2430
|
//#region src/db/migrations/005_cold_store_chunk_counter.ts
|
|
2425
2431
|
var _005_cold_store_chunk_counter_exports = /* @__PURE__ */ __exportAll({
|
|
2426
|
-
down: () => down$
|
|
2427
|
-
up: () => up$
|
|
2432
|
+
down: () => down$37,
|
|
2433
|
+
up: () => up$37
|
|
2428
2434
|
});
|
|
2429
2435
|
/**
|
|
2430
2436
|
* Cold-store chunk counter table.
|
|
@@ -2441,7 +2447,7 @@ var _005_cold_store_chunk_counter_exports = /* @__PURE__ */ __exportAll({
|
|
|
2441
2447
|
*
|
|
2442
2448
|
* sections 5 and 8.
|
|
2443
2449
|
*/
|
|
2444
|
-
async function up$
|
|
2450
|
+
async function up$37(db) {
|
|
2445
2451
|
await sql`
|
|
2446
2452
|
CREATE TABLE public.cold_store_chunk_counts (
|
|
2447
2453
|
db TEXT NOT NULL,
|
|
@@ -2459,14 +2465,14 @@ async function up$29(db) {
|
|
|
2459
2465
|
ON public.cold_store_chunk_counts (db, table_name)
|
|
2460
2466
|
`.execute(db);
|
|
2461
2467
|
}
|
|
2462
|
-
async function down$
|
|
2468
|
+
async function down$37(db) {
|
|
2463
2469
|
await sql`DROP TABLE IF EXISTS public.cold_store_chunk_counts`.execute(db);
|
|
2464
2470
|
}
|
|
2465
2471
|
//#endregion
|
|
2466
2472
|
//#region src/db/migrations/006_runs_jobs_steps_archived_at.ts
|
|
2467
2473
|
var _006_runs_jobs_steps_archived_at_exports = /* @__PURE__ */ __exportAll({
|
|
2468
|
-
down: () => down$
|
|
2469
|
-
up: () => up$
|
|
2474
|
+
down: () => down$36,
|
|
2475
|
+
up: () => up$36
|
|
2470
2476
|
});
|
|
2471
2477
|
/**
|
|
2472
2478
|
* `execution_runs` / `execution_jobs` / `execution_steps` cold-store
|
|
@@ -2502,7 +2508,7 @@ var _006_runs_jobs_steps_archived_at_exports = /* @__PURE__ */ __exportAll({
|
|
|
2502
2508
|
* - `idx_execution_jobs_routing_key_created (routing_key, created_at)`
|
|
2503
2509
|
* - `idx_execution_steps_routing_key_created (routing_key, created_at)`
|
|
2504
2510
|
*/
|
|
2505
|
-
async function up$
|
|
2511
|
+
async function up$36(db) {
|
|
2506
2512
|
await sql`
|
|
2507
2513
|
ALTER TABLE public.execution_runs
|
|
2508
2514
|
ADD COLUMN archived_at TIMESTAMPTZ NULL,
|
|
@@ -2547,7 +2553,7 @@ async function up$28(db) {
|
|
|
2547
2553
|
ON public.execution_steps (routing_key, created_at)
|
|
2548
2554
|
`.execute(db);
|
|
2549
2555
|
}
|
|
2550
|
-
async function down$
|
|
2556
|
+
async function down$36(db) {
|
|
2551
2557
|
await sql`DROP INDEX IF EXISTS public.idx_execution_steps_routing_key_created`.execute(db);
|
|
2552
2558
|
await sql`
|
|
2553
2559
|
ALTER TABLE public.execution_steps
|
|
@@ -2572,8 +2578,8 @@ async function down$28(db) {
|
|
|
2572
2578
|
//#endregion
|
|
2573
2579
|
//#region src/db/migrations/007_audit_logs_archived_at.ts
|
|
2574
2580
|
var _007_audit_logs_archived_at_exports = /* @__PURE__ */ __exportAll({
|
|
2575
|
-
down: () => down$
|
|
2576
|
-
up: () => up$
|
|
2581
|
+
down: () => down$35,
|
|
2582
|
+
up: () => up$35
|
|
2577
2583
|
});
|
|
2578
2584
|
/**
|
|
2579
2585
|
* `secret_audit_log` and `access_log` cold-store schema additions, plus
|
|
@@ -2613,7 +2619,7 @@ var _007_audit_logs_archived_at_exports = /* @__PURE__ */ __exportAll({
|
|
|
2613
2619
|
* `down()` would not have meaningful retention bounds. Acceptable for
|
|
2614
2620
|
* staging.
|
|
2615
2621
|
*/
|
|
2616
|
-
async function up$
|
|
2622
|
+
async function up$35(db) {
|
|
2617
2623
|
await sql`
|
|
2618
2624
|
ALTER TABLE public.secret_audit_log
|
|
2619
2625
|
ADD COLUMN archived_at TIMESTAMPTZ NULL,
|
|
@@ -2634,7 +2640,7 @@ async function up$27(db) {
|
|
|
2634
2640
|
DROP COLUMN IF EXISTS expires_at
|
|
2635
2641
|
`.execute(db);
|
|
2636
2642
|
}
|
|
2637
|
-
async function down$
|
|
2643
|
+
async function down$35(db) {
|
|
2638
2644
|
await sql`
|
|
2639
2645
|
ALTER TABLE public.access_log
|
|
2640
2646
|
ADD COLUMN expires_at TIMESTAMPTZ NOT NULL DEFAULT (now() + INTERVAL '90 days')
|
|
@@ -2662,8 +2668,8 @@ async function down$27(db) {
|
|
|
2662
2668
|
//#endregion
|
|
2663
2669
|
//#region src/db/migrations/008_event_log_archived_at.ts
|
|
2664
2670
|
var _008_event_log_archived_at_exports = /* @__PURE__ */ __exportAll({
|
|
2665
|
-
down: () => down$
|
|
2666
|
-
up: () => up$
|
|
2671
|
+
down: () => down$34,
|
|
2672
|
+
up: () => up$34
|
|
2667
2673
|
});
|
|
2668
2674
|
/**
|
|
2669
2675
|
* `event_log` cold-store schema additions plus removal of the
|
|
@@ -2701,7 +2707,7 @@ var _008_event_log_archived_at_exports = /* @__PURE__ */ __exportAll({
|
|
|
2701
2707
|
* — best effort; rows inserted between `up()` and a hypothetical
|
|
2702
2708
|
* `down()` would not have meaningful retention bounds.
|
|
2703
2709
|
*/
|
|
2704
|
-
async function up$
|
|
2710
|
+
async function up$34(db) {
|
|
2705
2711
|
await sql`
|
|
2706
2712
|
ALTER TABLE public.event_log
|
|
2707
2713
|
ADD COLUMN archived_at TIMESTAMPTZ NULL,
|
|
@@ -2717,7 +2723,7 @@ async function up$26(db) {
|
|
|
2717
2723
|
DROP COLUMN IF EXISTS expires_at
|
|
2718
2724
|
`.execute(db);
|
|
2719
2725
|
}
|
|
2720
|
-
async function down$
|
|
2726
|
+
async function down$34(db) {
|
|
2721
2727
|
await sql`
|
|
2722
2728
|
ALTER TABLE public.event_log
|
|
2723
2729
|
ADD COLUMN expires_at TIMESTAMPTZ NOT NULL DEFAULT (now() + INTERVAL '30 days')
|
|
@@ -2736,8 +2742,8 @@ async function down$26(db) {
|
|
|
2736
2742
|
//#endregion
|
|
2737
2743
|
//#region src/db/migrations/009_access_log_trigram.ts
|
|
2738
2744
|
var _009_access_log_trigram_exports = /* @__PURE__ */ __exportAll({
|
|
2739
|
-
down: () => down$
|
|
2740
|
-
up: () => up$
|
|
2745
|
+
down: () => down$33,
|
|
2746
|
+
up: () => up$33
|
|
2741
2747
|
});
|
|
2742
2748
|
/**
|
|
2743
2749
|
* Trigram (pg_trgm) index on access_log.error_message for the federated
|
|
@@ -2750,7 +2756,7 @@ var _009_access_log_trigram_exports = /* @__PURE__ */ __exportAll({
|
|
|
2750
2756
|
* EXISTS` are both safe to re-run. No CONCURRENTLY because Kysely runs
|
|
2751
2757
|
* migrations inside a transaction; the lock is brief on a sampled table.
|
|
2752
2758
|
*/
|
|
2753
|
-
async function up$
|
|
2759
|
+
async function up$33(db) {
|
|
2754
2760
|
await sql`CREATE EXTENSION IF NOT EXISTS pg_trgm`.execute(db);
|
|
2755
2761
|
await sql`
|
|
2756
2762
|
CREATE INDEX IF NOT EXISTS access_log_error_message_trgm_idx
|
|
@@ -2759,14 +2765,14 @@ async function up$25(db) {
|
|
|
2759
2765
|
WHERE error_message IS NOT NULL
|
|
2760
2766
|
`.execute(db);
|
|
2761
2767
|
}
|
|
2762
|
-
async function down$
|
|
2768
|
+
async function down$33(db) {
|
|
2763
2769
|
await sql`DROP INDEX IF EXISTS public.access_log_error_message_trgm_idx`.execute(db);
|
|
2764
2770
|
}
|
|
2765
2771
|
//#endregion
|
|
2766
2772
|
//#region src/db/migrations/010_cold_store_chunks.ts
|
|
2767
2773
|
var _010_cold_store_chunks_exports = /* @__PURE__ */ __exportAll({
|
|
2768
|
-
down: () => down$
|
|
2769
|
-
up: () => up$
|
|
2774
|
+
down: () => down$32,
|
|
2775
|
+
up: () => up$32
|
|
2770
2776
|
});
|
|
2771
2777
|
/**
|
|
2772
2778
|
* Cold-store chunk index — Phase 2 (cold-store purge).
|
|
@@ -2798,7 +2804,7 @@ var _010_cold_store_chunks_exports = /* @__PURE__ */ __exportAll({
|
|
|
2798
2804
|
* forever. Adapters that don't opt into per-bucket archival via
|
|
2799
2805
|
* `coldTtlDays` don't insert here either.
|
|
2800
2806
|
*/
|
|
2801
|
-
async function up$
|
|
2807
|
+
async function up$32(db) {
|
|
2802
2808
|
await sql`
|
|
2803
2809
|
CREATE TABLE public.cold_store_chunks (
|
|
2804
2810
|
db TEXT NOT NULL,
|
|
@@ -2825,14 +2831,14 @@ async function up$24(db) {
|
|
|
2825
2831
|
ON public.cold_store_chunks (db, table_name, tenant_id, archived_at DESC)
|
|
2826
2832
|
`.execute(db);
|
|
2827
2833
|
}
|
|
2828
|
-
async function down$
|
|
2834
|
+
async function down$32(db) {
|
|
2829
2835
|
await sql`DROP TABLE IF EXISTS public.cold_store_chunks`.execute(db);
|
|
2830
2836
|
}
|
|
2831
2837
|
//#endregion
|
|
2832
2838
|
//#region src/db/migrations/011_drop_source_secrets_notify.ts
|
|
2833
2839
|
var _011_drop_source_secrets_notify_exports = /* @__PURE__ */ __exportAll({
|
|
2834
|
-
down: () => down$
|
|
2835
|
-
up: () => up$
|
|
2840
|
+
down: () => down$31,
|
|
2841
|
+
up: () => up$31
|
|
2836
2842
|
});
|
|
2837
2843
|
/**
|
|
2838
2844
|
* Drop the `source_secrets_change_trigger` and the
|
|
@@ -2851,11 +2857,11 @@ var _011_drop_source_secrets_notify_exports = /* @__PURE__ */ __exportAll({
|
|
|
2851
2857
|
* in `001_initial.ts`. They wake up no consumer until the `WebhookSecretManager`
|
|
2852
2858
|
* is restored, so this migration is safe to roll back.
|
|
2853
2859
|
*/
|
|
2854
|
-
async function up$
|
|
2860
|
+
async function up$31(db) {
|
|
2855
2861
|
await sql`DROP TRIGGER IF EXISTS source_secrets_change_trigger ON public.scoped_secrets`.execute(db);
|
|
2856
2862
|
await sql`DROP FUNCTION IF EXISTS public.notify_source_secrets_change() CASCADE`.execute(db);
|
|
2857
2863
|
}
|
|
2858
|
-
async function down$
|
|
2864
|
+
async function down$31(db) {
|
|
2859
2865
|
await sql`
|
|
2860
2866
|
CREATE OR REPLACE FUNCTION public.notify_source_secrets_change() RETURNS trigger
|
|
2861
2867
|
LANGUAGE plpgsql
|
|
@@ -2894,8 +2900,8 @@ async function down$23(db) {
|
|
|
2894
2900
|
//#endregion
|
|
2895
2901
|
//#region src/db/migrations/012_peer_credentials_active_uniq.ts
|
|
2896
2902
|
var _012_peer_credentials_active_uniq_exports = /* @__PURE__ */ __exportAll({
|
|
2897
|
-
down: () => down$
|
|
2898
|
-
up: () => up$
|
|
2903
|
+
down: () => down$30,
|
|
2904
|
+
up: () => up$30
|
|
2899
2905
|
});
|
|
2900
2906
|
/**
|
|
2901
2907
|
* Add a partial unique index on `peer_credentials (instance_id) WHERE
|
|
@@ -2921,7 +2927,7 @@ var _012_peer_credentials_active_uniq_exports = /* @__PURE__ */ __exportAll({
|
|
|
2921
2927
|
* `down()` only drops the index; it does NOT undo the dedupe (there's no
|
|
2922
2928
|
* safe way to recreate revoked rows, and the dedupe is monotonic).
|
|
2923
2929
|
*/
|
|
2924
|
-
async function up$
|
|
2930
|
+
async function up$30(db) {
|
|
2925
2931
|
await sql`
|
|
2926
2932
|
UPDATE public.peer_credentials
|
|
2927
2933
|
SET revoked_at = NOW()
|
|
@@ -2939,14 +2945,14 @@ async function up$22(db) {
|
|
|
2939
2945
|
WHERE revoked_at IS NULL
|
|
2940
2946
|
`.execute(db);
|
|
2941
2947
|
}
|
|
2942
|
-
async function down$
|
|
2948
|
+
async function down$30(db) {
|
|
2943
2949
|
await sql`DROP INDEX IF EXISTS public.peer_credentials_active_uniq`.execute(db);
|
|
2944
2950
|
}
|
|
2945
2951
|
//#endregion
|
|
2946
2952
|
//#region src/db/migrations/013_execution_log_bytes.ts
|
|
2947
2953
|
var _013_execution_log_bytes_exports = /* @__PURE__ */ __exportAll({
|
|
2948
|
-
down: () => down$
|
|
2949
|
-
up: () => up$
|
|
2954
|
+
down: () => down$29,
|
|
2955
|
+
up: () => up$29
|
|
2950
2956
|
});
|
|
2951
2957
|
/**
|
|
2952
2958
|
* Add `log_bytes BIGINT NOT NULL DEFAULT 0` columns to `execution_runs` and
|
|
@@ -2965,7 +2971,7 @@ var _013_execution_log_bytes_exports = /* @__PURE__ */ __exportAll({
|
|
|
2965
2971
|
*
|
|
2966
2972
|
* Idempotent (`ADD COLUMN IF NOT EXISTS`).
|
|
2967
2973
|
*/
|
|
2968
|
-
async function up$
|
|
2974
|
+
async function up$29(db) {
|
|
2969
2975
|
await sql`
|
|
2970
2976
|
ALTER TABLE public.execution_runs
|
|
2971
2977
|
ADD COLUMN IF NOT EXISTS log_bytes BIGINT NOT NULL DEFAULT 0
|
|
@@ -2975,15 +2981,15 @@ async function up$21(db) {
|
|
|
2975
2981
|
ADD COLUMN IF NOT EXISTS log_bytes BIGINT NOT NULL DEFAULT 0
|
|
2976
2982
|
`.execute(db);
|
|
2977
2983
|
}
|
|
2978
|
-
async function down$
|
|
2984
|
+
async function down$29(db) {
|
|
2979
2985
|
await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS log_bytes`.execute(db);
|
|
2980
2986
|
await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS log_bytes`.execute(db);
|
|
2981
2987
|
}
|
|
2982
2988
|
//#endregion
|
|
2983
2989
|
//#region src/db/migrations/014_kici_events_lease_retry.ts
|
|
2984
2990
|
var _014_kici_events_lease_retry_exports = /* @__PURE__ */ __exportAll({
|
|
2985
|
-
down: () => down$
|
|
2986
|
-
up: () => up$
|
|
2991
|
+
down: () => down$28,
|
|
2992
|
+
up: () => up$28
|
|
2987
2993
|
});
|
|
2988
2994
|
/**
|
|
2989
2995
|
* Add lease + retry + DLQ columns to `kici_events` so the EventRouter can
|
|
@@ -3017,7 +3023,7 @@ var _014_kici_events_lease_retry_exports = /* @__PURE__ */ __exportAll({
|
|
|
3017
3023
|
*
|
|
3018
3024
|
* Idempotent (`ADD COLUMN IF NOT EXISTS` + `CREATE INDEX IF NOT EXISTS`).
|
|
3019
3025
|
*/
|
|
3020
|
-
async function up$
|
|
3026
|
+
async function up$28(db) {
|
|
3021
3027
|
await sql`
|
|
3022
3028
|
ALTER TABLE public.kici_events
|
|
3023
3029
|
ADD COLUMN IF NOT EXISTS claimed_at TIMESTAMPTZ,
|
|
@@ -3048,7 +3054,7 @@ async function up$20(db) {
|
|
|
3048
3054
|
WHERE dlq_at IS NOT NULL
|
|
3049
3055
|
`.execute(db);
|
|
3050
3056
|
}
|
|
3051
|
-
async function down$
|
|
3057
|
+
async function down$28(db) {
|
|
3052
3058
|
await sql`DROP INDEX IF EXISTS public.idx_kici_events_dlq`.execute(db);
|
|
3053
3059
|
await sql`DROP INDEX IF EXISTS public.idx_kici_events_lease_expired`.execute(db);
|
|
3054
3060
|
await sql`DROP INDEX IF EXISTS public.idx_kici_events_retry_due`.execute(db);
|
|
@@ -3066,8 +3072,8 @@ async function down$20(db) {
|
|
|
3066
3072
|
//#endregion
|
|
3067
3073
|
//#region src/db/migrations/015_org_settings_customer_scoped.ts
|
|
3068
3074
|
var _015_org_settings_customer_scoped_exports = /* @__PURE__ */ __exportAll({
|
|
3069
|
-
down: () => down$
|
|
3070
|
-
up: () => up$
|
|
3075
|
+
down: () => down$27,
|
|
3076
|
+
up: () => up$27
|
|
3071
3077
|
});
|
|
3072
3078
|
/**
|
|
3073
3079
|
* Org-scope `org_settings` and qualify each glob entry by source.
|
|
@@ -3095,7 +3101,7 @@ var _015_org_settings_customer_scoped_exports = /* @__PURE__ */ __exportAll({
|
|
|
3095
3101
|
* Idempotent: a re-run on an already-migrated DB sees `customer_id` exists
|
|
3096
3102
|
* and the list columns are already jsonb, so it is a no-op.
|
|
3097
3103
|
*/
|
|
3098
|
-
async function up$
|
|
3104
|
+
async function up$27(db) {
|
|
3099
3105
|
if ((await sql`
|
|
3100
3106
|
SELECT EXISTS (
|
|
3101
3107
|
SELECT 1 FROM information_schema.columns
|
|
@@ -3220,7 +3226,7 @@ async function up$19(db) {
|
|
|
3220
3226
|
await sql`DROP TABLE _org_settings_merged`.execute(db);
|
|
3221
3227
|
await sql`DROP TABLE _org_settings_stage`.execute(db);
|
|
3222
3228
|
}
|
|
3223
|
-
async function down$
|
|
3229
|
+
async function down$27(db) {
|
|
3224
3230
|
if (!(await sql`
|
|
3225
3231
|
SELECT EXISTS (
|
|
3226
3232
|
SELECT 1 FROM information_schema.columns
|
|
@@ -3245,8 +3251,8 @@ async function down$19(db) {
|
|
|
3245
3251
|
//#endregion
|
|
3246
3252
|
//#region src/db/migrations/016_org_settings_allow_http_npm.ts
|
|
3247
3253
|
var _016_org_settings_allow_http_npm_exports = /* @__PURE__ */ __exportAll({
|
|
3248
|
-
down: () => down$
|
|
3249
|
-
up: () => up$
|
|
3254
|
+
down: () => down$26,
|
|
3255
|
+
up: () => up$26
|
|
3250
3256
|
});
|
|
3251
3257
|
/**
|
|
3252
3258
|
* Add `org_settings.allow_http_npm_registries boolean NOT NULL DEFAULT false`.
|
|
@@ -3259,7 +3265,7 @@ var _016_org_settings_allow_http_npm_exports = /* @__PURE__ */ __exportAll({
|
|
|
3259
3265
|
*
|
|
3260
3266
|
* Idempotent: a re-run on a DB that already has the column is a no-op.
|
|
3261
3267
|
*/
|
|
3262
|
-
async function up$
|
|
3268
|
+
async function up$26(db) {
|
|
3263
3269
|
if ((await sql`
|
|
3264
3270
|
SELECT EXISTS (
|
|
3265
3271
|
SELECT 1 FROM information_schema.columns
|
|
@@ -3273,7 +3279,7 @@ async function up$18(db) {
|
|
|
3273
3279
|
ADD COLUMN allow_http_npm_registries boolean NOT NULL DEFAULT false
|
|
3274
3280
|
`.execute(db);
|
|
3275
3281
|
}
|
|
3276
|
-
async function down$
|
|
3282
|
+
async function down$26(db) {
|
|
3277
3283
|
await sql`
|
|
3278
3284
|
ALTER TABLE public.org_settings DROP COLUMN IF EXISTS allow_http_npm_registries
|
|
3279
3285
|
`.execute(db);
|
|
@@ -3281,8 +3287,8 @@ async function down$18(db) {
|
|
|
3281
3287
|
//#endregion
|
|
3282
3288
|
//#region src/db/migrations/017_org_id_widen.ts
|
|
3283
3289
|
var _017_org_id_widen_exports = /* @__PURE__ */ __exportAll({
|
|
3284
|
-
down: () => down$
|
|
3285
|
-
up: () => up$
|
|
3290
|
+
down: () => down$25,
|
|
3291
|
+
up: () => up$25
|
|
3286
3292
|
});
|
|
3287
3293
|
/**
|
|
3288
3294
|
* Widen every orchestrator-side `org_id varchar(12)` column to
|
|
@@ -3322,17 +3328,17 @@ const ORG_ID_TABLES$1 = [
|
|
|
3322
3328
|
"held_runs",
|
|
3323
3329
|
"scoped_secrets"
|
|
3324
3330
|
];
|
|
3325
|
-
async function up$
|
|
3331
|
+
async function up$25(db) {
|
|
3326
3332
|
for (const table of ORG_ID_TABLES$1) await sql.raw(`ALTER TABLE public.${table} ALTER COLUMN org_id TYPE varchar(16)`).execute(db);
|
|
3327
3333
|
}
|
|
3328
|
-
async function down$
|
|
3334
|
+
async function down$25(db) {
|
|
3329
3335
|
for (const table of ORG_ID_TABLES$1) await sql.raw(`ALTER TABLE public.${table} ALTER COLUMN org_id TYPE varchar(12)`).execute(db);
|
|
3330
3336
|
}
|
|
3331
3337
|
//#endregion
|
|
3332
3338
|
//#region src/db/migrations/018_org_id_prefix_backfill.ts
|
|
3333
3339
|
var _018_org_id_prefix_backfill_exports = /* @__PURE__ */ __exportAll({
|
|
3334
|
-
down: () => down$
|
|
3335
|
-
up: () => up$
|
|
3340
|
+
down: () => down$24,
|
|
3341
|
+
up: () => up$24
|
|
3336
3342
|
});
|
|
3337
3343
|
/**
|
|
3338
3344
|
* Prefix every orchestrator-side tenant string with `org_` to align
|
|
@@ -3377,19 +3383,19 @@ const CUSTOMER_ID_TABLES = [
|
|
|
3377
3383
|
"workflow_registrations",
|
|
3378
3384
|
"org_settings"
|
|
3379
3385
|
];
|
|
3380
|
-
async function up$
|
|
3386
|
+
async function up$24(db) {
|
|
3381
3387
|
for (const table of ORG_ID_TABLES) await sql.raw(`UPDATE public.${table} SET org_id = 'org_' || org_id WHERE org_id <> 'kici-admin' AND org_id NOT LIKE 'org\\_%' ESCAPE '\\'`).execute(db);
|
|
3382
3388
|
for (const table of CUSTOMER_ID_TABLES) await sql.raw(`UPDATE public.${table} SET customer_id = 'org_' || customer_id WHERE customer_id <> 'kici-admin' AND customer_id NOT LIKE 'org\\_%' ESCAPE '\\'`).execute(db);
|
|
3383
3389
|
}
|
|
3384
|
-
async function down$
|
|
3390
|
+
async function down$24(db) {
|
|
3385
3391
|
for (const table of CUSTOMER_ID_TABLES) await sql.raw(`UPDATE public.${table} SET customer_id = substring(customer_id from 5) WHERE customer_id LIKE 'org\\_%' ESCAPE '\\'`).execute(db);
|
|
3386
3392
|
for (const table of ORG_ID_TABLES) await sql.raw(`UPDATE public.${table} SET org_id = substring(org_id from 5) WHERE org_id LIKE 'org\\_%' ESCAPE '\\'`).execute(db);
|
|
3387
3393
|
}
|
|
3388
3394
|
//#endregion
|
|
3389
3395
|
//#region src/db/migrations/019_generic_sources_change_notify.ts
|
|
3390
3396
|
var _019_generic_sources_change_notify_exports = /* @__PURE__ */ __exportAll({
|
|
3391
|
-
down: () => down$
|
|
3392
|
-
up: () => up$
|
|
3397
|
+
down: () => down$23,
|
|
3398
|
+
up: () => up$23
|
|
3393
3399
|
});
|
|
3394
3400
|
/**
|
|
3395
3401
|
* Add a Postgres trigger on `generic_webhook_sources` that emits
|
|
@@ -3412,7 +3418,7 @@ var _019_generic_sources_change_notify_exports = /* @__PURE__ */ __exportAll({
|
|
|
3412
3418
|
* (`notify_sources_change()` + `sources_change_trigger`, defined in
|
|
3413
3419
|
* `001_initial.ts`).
|
|
3414
3420
|
*/
|
|
3415
|
-
async function up$
|
|
3421
|
+
async function up$23(db) {
|
|
3416
3422
|
await sql`
|
|
3417
3423
|
CREATE FUNCTION public.notify_generic_sources_change() RETURNS trigger
|
|
3418
3424
|
LANGUAGE plpgsql
|
|
@@ -3433,15 +3439,15 @@ async function up$15(db) {
|
|
|
3433
3439
|
FOR EACH ROW EXECUTE FUNCTION public.notify_generic_sources_change()
|
|
3434
3440
|
`.execute(db);
|
|
3435
3441
|
}
|
|
3436
|
-
async function down$
|
|
3442
|
+
async function down$23(db) {
|
|
3437
3443
|
await sql`DROP TRIGGER IF EXISTS generic_sources_change_trigger ON public.generic_webhook_sources`.execute(db);
|
|
3438
3444
|
await sql`DROP FUNCTION IF EXISTS public.notify_generic_sources_change()`.execute(db);
|
|
3439
3445
|
}
|
|
3440
3446
|
//#endregion
|
|
3441
3447
|
//#region src/db/migrations/020_org_settings_dashboard_write_policy.ts
|
|
3442
3448
|
var _020_org_settings_dashboard_write_policy_exports = /* @__PURE__ */ __exportAll({
|
|
3443
|
-
down: () => down$
|
|
3444
|
-
up: () => up$
|
|
3449
|
+
down: () => down$22,
|
|
3450
|
+
up: () => up$22
|
|
3445
3451
|
});
|
|
3446
3452
|
/**
|
|
3447
3453
|
* Add `org_settings.dashboard_write_policy jsonb NOT NULL DEFAULT '{}'`.
|
|
@@ -3456,7 +3462,7 @@ var _020_org_settings_dashboard_write_policy_exports = /* @__PURE__ */ __exportA
|
|
|
3456
3462
|
*
|
|
3457
3463
|
* Idempotent: a re-run on a DB that already has the column is a no-op.
|
|
3458
3464
|
*/
|
|
3459
|
-
async function up$
|
|
3465
|
+
async function up$22(db) {
|
|
3460
3466
|
if ((await sql`
|
|
3461
3467
|
SELECT EXISTS (
|
|
3462
3468
|
SELECT 1 FROM information_schema.columns
|
|
@@ -3470,7 +3476,7 @@ async function up$14(db) {
|
|
|
3470
3476
|
ADD COLUMN dashboard_write_policy jsonb NOT NULL DEFAULT '{}'::jsonb
|
|
3471
3477
|
`.execute(db);
|
|
3472
3478
|
}
|
|
3473
|
-
async function down$
|
|
3479
|
+
async function down$22(db) {
|
|
3474
3480
|
await sql`
|
|
3475
3481
|
ALTER TABLE public.org_settings DROP COLUMN IF EXISTS dashboard_write_policy
|
|
3476
3482
|
`.execute(db);
|
|
@@ -3478,8 +3484,8 @@ async function down$14(db) {
|
|
|
3478
3484
|
//#endregion
|
|
3479
3485
|
//#region src/db/migrations/021_check_run_tracking.ts
|
|
3480
3486
|
var _021_check_run_tracking_exports = /* @__PURE__ */ __exportAll({
|
|
3481
|
-
down: () => down$
|
|
3482
|
-
up: () => up$
|
|
3487
|
+
down: () => down$21,
|
|
3488
|
+
up: () => up$21
|
|
3483
3489
|
});
|
|
3484
3490
|
/**
|
|
3485
3491
|
* Add `check_run_tracking` table for HA-safe check-run state persistence.
|
|
@@ -3503,7 +3509,7 @@ var _021_check_run_tracking_exports = /* @__PURE__ */ __exportAll({
|
|
|
3503
3509
|
*
|
|
3504
3510
|
* Idempotent: a re-run on a DB that already has the table is a no-op.
|
|
3505
3511
|
*/
|
|
3506
|
-
async function up$
|
|
3512
|
+
async function up$21(db) {
|
|
3507
3513
|
if ((await sql`
|
|
3508
3514
|
SELECT EXISTS (
|
|
3509
3515
|
SELECT 1 FROM information_schema.tables
|
|
@@ -3534,14 +3540,14 @@ async function up$13(db) {
|
|
|
3534
3540
|
WHERE run_id IS NOT NULL
|
|
3535
3541
|
`.execute(db);
|
|
3536
3542
|
}
|
|
3537
|
-
async function down$
|
|
3543
|
+
async function down$21(db) {
|
|
3538
3544
|
await sql`DROP TABLE IF EXISTS public.check_run_tracking`.execute(db);
|
|
3539
3545
|
}
|
|
3540
3546
|
//#endregion
|
|
3541
3547
|
//#region src/db/migrations/022_scaler_manager_state.ts
|
|
3542
3548
|
var _022_scaler_manager_state_exports = /* @__PURE__ */ __exportAll({
|
|
3543
|
-
down: () => down$
|
|
3544
|
-
up: () => up$
|
|
3549
|
+
down: () => down$20,
|
|
3550
|
+
up: () => up$20
|
|
3545
3551
|
});
|
|
3546
3552
|
/**
|
|
3547
3553
|
* Add three tables persisting `ScalerManager` per-coord state:
|
|
@@ -3568,7 +3574,7 @@ var _022_scaler_manager_state_exports = /* @__PURE__ */ __exportAll({
|
|
|
3568
3574
|
* Idempotent: a re-run on a DB that already has any of these tables
|
|
3569
3575
|
* leaves the existing one alone.
|
|
3570
3576
|
*/
|
|
3571
|
-
async function up$
|
|
3577
|
+
async function up$20(db) {
|
|
3572
3578
|
const tableExists = async (name) => {
|
|
3573
3579
|
return (await sql`
|
|
3574
3580
|
SELECT EXISTS (
|
|
@@ -3618,7 +3624,7 @@ async function up$12(db) {
|
|
|
3618
3624
|
`.execute(db);
|
|
3619
3625
|
}
|
|
3620
3626
|
}
|
|
3621
|
-
async function down$
|
|
3627
|
+
async function down$20(db) {
|
|
3622
3628
|
await sql`DROP TABLE IF EXISTS public.scaler_reservations`.execute(db);
|
|
3623
3629
|
await sql`DROP TABLE IF EXISTS public.scaler_agent_jobs`.execute(db);
|
|
3624
3630
|
await sql`DROP TABLE IF EXISTS public.scaler_spawning_agents`.execute(db);
|
|
@@ -3626,8 +3632,8 @@ async function down$12(db) {
|
|
|
3626
3632
|
//#endregion
|
|
3627
3633
|
//#region src/db/migrations/023_dispatch_queue_recovery_deadline.ts
|
|
3628
3634
|
var _023_dispatch_queue_recovery_deadline_exports = /* @__PURE__ */ __exportAll({
|
|
3629
|
-
down: () => down$
|
|
3630
|
-
up: () => up$
|
|
3635
|
+
down: () => down$19,
|
|
3636
|
+
up: () => up$19
|
|
3631
3637
|
});
|
|
3632
3638
|
/**
|
|
3633
3639
|
* Add `dispatch_queue.recovery_deadline TIMESTAMPTZ` and
|
|
@@ -3651,7 +3657,7 @@ var _023_dispatch_queue_recovery_deadline_exports = /* @__PURE__ */ __exportAll(
|
|
|
3651
3657
|
* Idempotent: re-running on a DB that already has either column is a
|
|
3652
3658
|
* no-op.
|
|
3653
3659
|
*/
|
|
3654
|
-
async function up$
|
|
3660
|
+
async function up$19(db) {
|
|
3655
3661
|
const colExists = async (name) => {
|
|
3656
3662
|
return (await sql`
|
|
3657
3663
|
SELECT EXISTS (
|
|
@@ -3676,7 +3682,7 @@ async function up$11(db) {
|
|
|
3676
3682
|
WHERE recovery_deadline IS NOT NULL
|
|
3677
3683
|
`.execute(db);
|
|
3678
3684
|
}
|
|
3679
|
-
async function down$
|
|
3685
|
+
async function down$19(db) {
|
|
3680
3686
|
await sql`DROP INDEX IF EXISTS public.idx_dispatch_queue_recovery_deadline`.execute(db);
|
|
3681
3687
|
await sql`
|
|
3682
3688
|
ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS recovery_agent_id
|
|
@@ -3688,8 +3694,8 @@ async function down$11(db) {
|
|
|
3688
3694
|
//#endregion
|
|
3689
3695
|
//#region src/db/migrations/024_dispatch_queue_provisioning_error.ts
|
|
3690
3696
|
var _024_dispatch_queue_provisioning_error_exports = /* @__PURE__ */ __exportAll({
|
|
3691
|
-
down: () => down$
|
|
3692
|
-
up: () => up$
|
|
3697
|
+
down: () => down$18,
|
|
3698
|
+
up: () => up$18
|
|
3693
3699
|
});
|
|
3694
3700
|
/**
|
|
3695
3701
|
* Add `dispatch_queue.last_provisioning_error TEXT` recording the most
|
|
@@ -3705,7 +3711,7 @@ var _024_dispatch_queue_provisioning_error_exports = /* @__PURE__ */ __exportAll
|
|
|
3705
3711
|
*
|
|
3706
3712
|
* Idempotent: re-running on a DB that already has the column is a no-op.
|
|
3707
3713
|
*/
|
|
3708
|
-
async function up$
|
|
3714
|
+
async function up$18(db) {
|
|
3709
3715
|
const colExists = async (name) => {
|
|
3710
3716
|
return (await sql`
|
|
3711
3717
|
SELECT EXISTS (
|
|
@@ -3721,7 +3727,7 @@ async function up$10(db) {
|
|
|
3721
3727
|
ADD COLUMN last_provisioning_error TEXT
|
|
3722
3728
|
`.execute(db);
|
|
3723
3729
|
}
|
|
3724
|
-
async function down$
|
|
3730
|
+
async function down$18(db) {
|
|
3725
3731
|
await sql`
|
|
3726
3732
|
ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS last_provisioning_error
|
|
3727
3733
|
`.execute(db);
|
|
@@ -3729,8 +3735,8 @@ async function down$10(db) {
|
|
|
3729
3735
|
//#endregion
|
|
3730
3736
|
//#region src/db/migrations/025_init_failure.ts
|
|
3731
3737
|
var _025_init_failure_exports = /* @__PURE__ */ __exportAll({
|
|
3732
|
-
down: () => down$
|
|
3733
|
-
up: () => up$
|
|
3738
|
+
down: () => down$17,
|
|
3739
|
+
up: () => up$17
|
|
3734
3740
|
});
|
|
3735
3741
|
/**
|
|
3736
3742
|
* Add `init_failure jsonb` columns to `execution_runs` and `execution_jobs`.
|
|
@@ -3744,7 +3750,7 @@ var _025_init_failure_exports = /* @__PURE__ */ __exportAll({
|
|
|
3744
3750
|
* Idempotent: re-running on a DB that already has either column is a no-op
|
|
3745
3751
|
* for that column.
|
|
3746
3752
|
*/
|
|
3747
|
-
async function up$
|
|
3753
|
+
async function up$17(db) {
|
|
3748
3754
|
const colExists = async (table, name) => {
|
|
3749
3755
|
return (await sql`
|
|
3750
3756
|
SELECT EXISTS (
|
|
@@ -3764,7 +3770,7 @@ async function up$9(db) {
|
|
|
3764
3770
|
ADD COLUMN init_failure JSONB DEFAULT NULL
|
|
3765
3771
|
`.execute(db);
|
|
3766
3772
|
}
|
|
3767
|
-
async function down$
|
|
3773
|
+
async function down$17(db) {
|
|
3768
3774
|
await sql`
|
|
3769
3775
|
ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS init_failure
|
|
3770
3776
|
`.execute(db);
|
|
@@ -3775,8 +3781,8 @@ async function down$9(db) {
|
|
|
3775
3781
|
//#endregion
|
|
3776
3782
|
//#region src/db/migrations/026_event_log_lockfile_corrupt.ts
|
|
3777
3783
|
var _026_event_log_lockfile_corrupt_exports = /* @__PURE__ */ __exportAll({
|
|
3778
|
-
down: () => down$
|
|
3779
|
-
up: () => up$
|
|
3784
|
+
down: () => down$16,
|
|
3785
|
+
up: () => up$16
|
|
3780
3786
|
});
|
|
3781
3787
|
/**
|
|
3782
3788
|
* Extend the event_log.status CHECK constraint with 'lockfile_corrupt' so the
|
|
@@ -3785,7 +3791,7 @@ var _026_event_log_lockfile_corrupt_exports = /* @__PURE__ */ __exportAll({
|
|
|
3785
3791
|
*
|
|
3786
3792
|
* Idempotent: the DROP ... IF EXISTS / re-ADD pair re-runs cleanly.
|
|
3787
3793
|
*/
|
|
3788
|
-
async function up$
|
|
3794
|
+
async function up$16(db) {
|
|
3789
3795
|
await sql`ALTER TABLE event_log DROP CONSTRAINT IF EXISTS event_log_status_check`.execute(db);
|
|
3790
3796
|
await sql`
|
|
3791
3797
|
ALTER TABLE event_log ADD CONSTRAINT event_log_status_check
|
|
@@ -3795,7 +3801,7 @@ async function up$8(db) {
|
|
|
3795
3801
|
])))
|
|
3796
3802
|
`.execute(db);
|
|
3797
3803
|
}
|
|
3798
|
-
async function down$
|
|
3804
|
+
async function down$16(db) {
|
|
3799
3805
|
await sql`ALTER TABLE event_log DROP CONSTRAINT IF EXISTS event_log_status_check`.execute(db);
|
|
3800
3806
|
await sql`
|
|
3801
3807
|
ALTER TABLE event_log ADD CONSTRAINT event_log_status_check
|
|
@@ -3808,8 +3814,8 @@ async function down$8(db) {
|
|
|
3808
3814
|
//#endregion
|
|
3809
3815
|
//#region src/db/migrations/027_workflow_timeout.ts
|
|
3810
3816
|
var _027_workflow_timeout_exports = /* @__PURE__ */ __exportAll({
|
|
3811
|
-
down: () => down$
|
|
3812
|
-
up: () => up$
|
|
3817
|
+
down: () => down$15,
|
|
3818
|
+
up: () => up$15
|
|
3813
3819
|
});
|
|
3814
3820
|
/**
|
|
3815
3821
|
* Add `workflow_timeout_ms integer` to `execution_runs`.
|
|
@@ -3827,13 +3833,13 @@ var _027_workflow_timeout_exports = /* @__PURE__ */ __exportAll({
|
|
|
3827
3833
|
*
|
|
3828
3834
|
* Idempotent: re-running on a DB that already has the column is a no-op.
|
|
3829
3835
|
*/
|
|
3830
|
-
async function up$
|
|
3836
|
+
async function up$15(db) {
|
|
3831
3837
|
await sql`
|
|
3832
3838
|
ALTER TABLE public.execution_runs
|
|
3833
3839
|
ADD COLUMN IF NOT EXISTS workflow_timeout_ms INTEGER DEFAULT NULL
|
|
3834
3840
|
`.execute(db);
|
|
3835
3841
|
}
|
|
3836
|
-
async function down$
|
|
3842
|
+
async function down$15(db) {
|
|
3837
3843
|
await sql`
|
|
3838
3844
|
ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS workflow_timeout_ms
|
|
3839
3845
|
`.execute(db);
|
|
@@ -3841,8 +3847,8 @@ async function down$7(db) {
|
|
|
3841
3847
|
//#endregion
|
|
3842
3848
|
//#region src/db/migrations/028_org_settings_user_cache.ts
|
|
3843
3849
|
var _028_org_settings_user_cache_exports = /* @__PURE__ */ __exportAll({
|
|
3844
|
-
down: () => down$
|
|
3845
|
-
up: () => up$
|
|
3850
|
+
down: () => down$14,
|
|
3851
|
+
up: () => up$14
|
|
3846
3852
|
});
|
|
3847
3853
|
/**
|
|
3848
3854
|
* Add `org_settings.user_cache_quota_bytes bigint` and
|
|
@@ -3871,7 +3877,7 @@ async function columnExists(db, column) {
|
|
|
3871
3877
|
) AS exists
|
|
3872
3878
|
`.execute(db)).rows[0]?.exists ?? false;
|
|
3873
3879
|
}
|
|
3874
|
-
async function up$
|
|
3880
|
+
async function up$14(db) {
|
|
3875
3881
|
if (!await columnExists(db, "user_cache_quota_bytes")) await sql`
|
|
3876
3882
|
ALTER TABLE public.org_settings
|
|
3877
3883
|
ADD COLUMN user_cache_quota_bytes bigint
|
|
@@ -3881,7 +3887,7 @@ async function up$6(db) {
|
|
|
3881
3887
|
ADD COLUMN user_cache_ttl_ms bigint
|
|
3882
3888
|
`.execute(db);
|
|
3883
3889
|
}
|
|
3884
|
-
async function down$
|
|
3890
|
+
async function down$14(db) {
|
|
3885
3891
|
await sql`
|
|
3886
3892
|
ALTER TABLE public.org_settings DROP COLUMN IF EXISTS user_cache_quota_bytes
|
|
3887
3893
|
`.execute(db);
|
|
@@ -3892,8 +3898,8 @@ async function down$6(db) {
|
|
|
3892
3898
|
//#endregion
|
|
3893
3899
|
//#region src/db/migrations/029_dispatch_queue_attempts.ts
|
|
3894
3900
|
var _029_dispatch_queue_attempts_exports = /* @__PURE__ */ __exportAll({
|
|
3895
|
-
down: () => down$
|
|
3896
|
-
up: () => up$
|
|
3901
|
+
down: () => down$13,
|
|
3902
|
+
up: () => up$13
|
|
3897
3903
|
});
|
|
3898
3904
|
/**
|
|
3899
3905
|
* Add `dispatch_queue.dispatch_attempts INT NOT NULL DEFAULT 0`.
|
|
@@ -3907,7 +3913,7 @@ var _029_dispatch_queue_attempts_exports = /* @__PURE__ */ __exportAll({
|
|
|
3907
3913
|
*
|
|
3908
3914
|
* Idempotent: re-running on a DB that already has the column is a no-op.
|
|
3909
3915
|
*/
|
|
3910
|
-
async function up$
|
|
3916
|
+
async function up$13(db) {
|
|
3911
3917
|
if (!((await sql`
|
|
3912
3918
|
SELECT EXISTS (
|
|
3913
3919
|
SELECT 1 FROM information_schema.columns
|
|
@@ -3920,7 +3926,7 @@ async function up$5(db) {
|
|
|
3920
3926
|
ADD COLUMN dispatch_attempts INT NOT NULL DEFAULT 0
|
|
3921
3927
|
`.execute(db);
|
|
3922
3928
|
}
|
|
3923
|
-
async function down$
|
|
3929
|
+
async function down$13(db) {
|
|
3924
3930
|
await sql`
|
|
3925
3931
|
ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS dispatch_attempts
|
|
3926
3932
|
`.execute(db);
|
|
@@ -3928,8 +3934,8 @@ async function down$5(db) {
|
|
|
3928
3934
|
//#endregion
|
|
3929
3935
|
//#region src/db/migrations/030_held_runs_env_set_null.ts
|
|
3930
3936
|
var _030_held_runs_env_set_null_exports = /* @__PURE__ */ __exportAll({
|
|
3931
|
-
down: () => down$
|
|
3932
|
-
up: () => up$
|
|
3937
|
+
down: () => down$12,
|
|
3938
|
+
up: () => up$12
|
|
3933
3939
|
});
|
|
3934
3940
|
/**
|
|
3935
3941
|
* held_runs.environment_id becomes nullable with ON DELETE SET NULL so
|
|
@@ -3940,14 +3946,14 @@ var _030_held_runs_env_set_null_exports = /* @__PURE__ */ __exportAll({
|
|
|
3940
3946
|
* Idempotent: dropping the NOT NULL and the constraint are both no-ops on a
|
|
3941
3947
|
* re-run, and the constraint is re-created with the SET NULL action.
|
|
3942
3948
|
*/
|
|
3943
|
-
async function up$
|
|
3949
|
+
async function up$12(db) {
|
|
3944
3950
|
await sql`ALTER TABLE public.held_runs ALTER COLUMN environment_id DROP NOT NULL`.execute(db);
|
|
3945
3951
|
await sql`ALTER TABLE public.held_runs DROP CONSTRAINT IF EXISTS held_runs_environment_id_fkey`.execute(db);
|
|
3946
3952
|
await sql`ALTER TABLE public.held_runs
|
|
3947
3953
|
ADD CONSTRAINT held_runs_environment_id_fkey
|
|
3948
3954
|
FOREIGN KEY (environment_id) REFERENCES public.environments(id) ON DELETE SET NULL`.execute(db);
|
|
3949
3955
|
}
|
|
3950
|
-
async function down$
|
|
3956
|
+
async function down$12(db) {
|
|
3951
3957
|
await sql`ALTER TABLE public.held_runs DROP CONSTRAINT IF EXISTS held_runs_environment_id_fkey`.execute(db);
|
|
3952
3958
|
await sql`ALTER TABLE public.held_runs
|
|
3953
3959
|
ADD CONSTRAINT held_runs_environment_id_fkey
|
|
@@ -3957,8 +3963,8 @@ async function down$4(db) {
|
|
|
3957
3963
|
//#endregion
|
|
3958
3964
|
//#region src/db/migrations/031_dispatch_queue_ack_deadline.ts
|
|
3959
3965
|
var _031_dispatch_queue_ack_deadline_exports = /* @__PURE__ */ __exportAll({
|
|
3960
|
-
down: () => down$
|
|
3961
|
-
up: () => up$
|
|
3966
|
+
down: () => down$11,
|
|
3967
|
+
up: () => up$11
|
|
3962
3968
|
});
|
|
3963
3969
|
/**
|
|
3964
3970
|
* Add `dispatch_queue.ack_deadline TIMESTAMPTZ` and
|
|
@@ -3975,7 +3981,7 @@ var _031_dispatch_queue_ack_deadline_exports = /* @__PURE__ */ __exportAll({
|
|
|
3975
3981
|
*
|
|
3976
3982
|
* Idempotent: re-running on a DB that already has either column is a no-op.
|
|
3977
3983
|
*/
|
|
3978
|
-
async function up$
|
|
3984
|
+
async function up$11(db) {
|
|
3979
3985
|
const colExists = async (name) => {
|
|
3980
3986
|
return (await sql`
|
|
3981
3987
|
SELECT EXISTS (
|
|
@@ -4000,7 +4006,7 @@ async function up$3(db) {
|
|
|
4000
4006
|
WHERE ack_deadline IS NOT NULL
|
|
4001
4007
|
`.execute(db);
|
|
4002
4008
|
}
|
|
4003
|
-
async function down$
|
|
4009
|
+
async function down$11(db) {
|
|
4004
4010
|
await sql`DROP INDEX IF EXISTS public.idx_dispatch_queue_ack_deadline`.execute(db);
|
|
4005
4011
|
await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS ack_agent_id`.execute(db);
|
|
4006
4012
|
await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS ack_deadline`.execute(db);
|
|
@@ -4008,8 +4014,8 @@ async function down$3(db) {
|
|
|
4008
4014
|
//#endregion
|
|
4009
4015
|
//#region src/db/migrations/032_org_settings_dispatch_ack_timeout.ts
|
|
4010
4016
|
var _032_org_settings_dispatch_ack_timeout_exports = /* @__PURE__ */ __exportAll({
|
|
4011
|
-
down: () => down$
|
|
4012
|
-
up: () => up$
|
|
4017
|
+
down: () => down$10,
|
|
4018
|
+
up: () => up$10
|
|
4013
4019
|
});
|
|
4014
4020
|
/**
|
|
4015
4021
|
* Add `org_settings.dispatch_ack_timeout_ms BIGINT` (nullable).
|
|
@@ -4021,7 +4027,7 @@ var _032_org_settings_dispatch_ack_timeout_exports = /* @__PURE__ */ __exportAll
|
|
|
4021
4027
|
*
|
|
4022
4028
|
* Idempotent: a re-run on a DB that already has the column is a no-op.
|
|
4023
4029
|
*/
|
|
4024
|
-
async function up$
|
|
4030
|
+
async function up$10(db) {
|
|
4025
4031
|
if ((await sql`
|
|
4026
4032
|
SELECT EXISTS (
|
|
4027
4033
|
SELECT 1 FROM information_schema.columns
|
|
@@ -4035,7 +4041,7 @@ async function up$2(db) {
|
|
|
4035
4041
|
ADD COLUMN dispatch_ack_timeout_ms BIGINT
|
|
4036
4042
|
`.execute(db);
|
|
4037
4043
|
}
|
|
4038
|
-
async function down$
|
|
4044
|
+
async function down$10(db) {
|
|
4039
4045
|
await sql`
|
|
4040
4046
|
ALTER TABLE public.org_settings DROP COLUMN IF EXISTS dispatch_ack_timeout_ms
|
|
4041
4047
|
`.execute(db);
|
|
@@ -4043,8 +4049,8 @@ async function down$2(db) {
|
|
|
4043
4049
|
//#endregion
|
|
4044
4050
|
//#region src/db/migrations/033_org_settings_approval.ts
|
|
4045
4051
|
var _033_org_settings_approval_exports = /* @__PURE__ */ __exportAll({
|
|
4046
|
-
down: () => down$
|
|
4047
|
-
up: () => up$
|
|
4052
|
+
down: () => down$9,
|
|
4053
|
+
up: () => up$9
|
|
4048
4054
|
});
|
|
4049
4055
|
/**
|
|
4050
4056
|
* Add the two approval-policy columns to `org_settings`:
|
|
@@ -4061,7 +4067,7 @@ var _033_org_settings_approval_exports = /* @__PURE__ */ __exportAll({
|
|
|
4061
4067
|
* and the orchestrator admin route. Idempotent: a re-run on a DB that already
|
|
4062
4068
|
* has the columns is a no-op (each column is guarded independently).
|
|
4063
4069
|
*/
|
|
4064
|
-
async function up$
|
|
4070
|
+
async function up$9(db) {
|
|
4065
4071
|
const colExists = async (column) => {
|
|
4066
4072
|
return (await sql`
|
|
4067
4073
|
SELECT EXISTS (
|
|
@@ -4081,7 +4087,7 @@ async function up$1(db) {
|
|
|
4081
4087
|
ADD COLUMN allow_self_approval BOOLEAN NOT NULL DEFAULT true
|
|
4082
4088
|
`.execute(db);
|
|
4083
4089
|
}
|
|
4084
|
-
async function down$
|
|
4090
|
+
async function down$9(db) {
|
|
4085
4091
|
await sql`
|
|
4086
4092
|
ALTER TABLE public.org_settings DROP COLUMN IF EXISTS approval_expiry_seconds
|
|
4087
4093
|
`.execute(db);
|
|
@@ -4092,8 +4098,8 @@ async function down$1(db) {
|
|
|
4092
4098
|
//#endregion
|
|
4093
4099
|
//#region src/db/migrations/034_held_runs_generalize.ts
|
|
4094
4100
|
var _034_held_runs_generalize_exports = /* @__PURE__ */ __exportAll({
|
|
4095
|
-
down: () => down,
|
|
4096
|
-
up: () => up
|
|
4101
|
+
down: () => down$8,
|
|
4102
|
+
up: () => up$8
|
|
4097
4103
|
});
|
|
4098
4104
|
/**
|
|
4099
4105
|
* Generalize `held_runs` from an environment-only hold into the unified
|
|
@@ -4115,7 +4121,7 @@ var _034_held_runs_generalize_exports = /* @__PURE__ */ __exportAll({
|
|
|
4115
4121
|
* New `held_run_approvals` table: one row per approver decision, FK to
|
|
4116
4122
|
* `held_runs.id` (uuid) with ON DELETE CASCADE.
|
|
4117
4123
|
*/
|
|
4118
|
-
async function up(db) {
|
|
4124
|
+
async function up$8(db) {
|
|
4119
4125
|
const colExists = async (column) => {
|
|
4120
4126
|
return (await sql`
|
|
4121
4127
|
SELECT EXISTS (
|
|
@@ -4148,7 +4154,7 @@ async function up(db) {
|
|
|
4148
4154
|
ON public.held_run_approvals USING btree (held_run_id)
|
|
4149
4155
|
`.execute(db);
|
|
4150
4156
|
}
|
|
4151
|
-
async function down(db) {
|
|
4157
|
+
async function down$8(db) {
|
|
4152
4158
|
await sql`DROP TABLE IF EXISTS public.held_run_approvals`.execute(db);
|
|
4153
4159
|
await sql`ALTER TABLE public.held_runs DROP COLUMN IF EXISTS approval_requirement`.execute(db);
|
|
4154
4160
|
await sql`ALTER TABLE public.held_runs DROP COLUMN IF EXISTS trigger_source`.execute(db);
|
|
@@ -4156,6 +4162,359 @@ async function down(db) {
|
|
|
4156
4162
|
await sql`ALTER TABLE public.held_runs DROP COLUMN IF EXISTS hold_scope`.execute(db);
|
|
4157
4163
|
}
|
|
4158
4164
|
//#endregion
|
|
4165
|
+
//#region src/db/migrations/035_pending_workflow_contexts.ts
|
|
4166
|
+
var _035_pending_workflow_contexts_exports = /* @__PURE__ */ __exportAll({
|
|
4167
|
+
down: () => down$7,
|
|
4168
|
+
up: () => up$7
|
|
4169
|
+
});
|
|
4170
|
+
/**
|
|
4171
|
+
* Pending workflow dispatch context — backs resume of a workflow whose install
|
|
4172
|
+
* gate held. One row per held run: the serializable dispatch inputs needed to
|
|
4173
|
+
* rebuild the WorkflowDispatchContext when the hold releases (reviewer approve,
|
|
4174
|
+
* wait-timer expiry, concurrency slot free). The row is deleted once the resume
|
|
4175
|
+
* dispatch has been kicked off.
|
|
4176
|
+
*/
|
|
4177
|
+
async function up$7(db) {
|
|
4178
|
+
await sql`
|
|
4179
|
+
CREATE TABLE IF NOT EXISTS public.pending_workflow_contexts (
|
|
4180
|
+
run_id text PRIMARY KEY,
|
|
4181
|
+
org_id text NOT NULL,
|
|
4182
|
+
context jsonb NOT NULL,
|
|
4183
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL
|
|
4184
|
+
)
|
|
4185
|
+
`.execute(db);
|
|
4186
|
+
}
|
|
4187
|
+
async function down$7(db) {
|
|
4188
|
+
await sql`DROP TABLE IF EXISTS public.pending_workflow_contexts`.execute(db);
|
|
4189
|
+
}
|
|
4190
|
+
//#endregion
|
|
4191
|
+
//#region src/db/migrations/036_attestations.ts
|
|
4192
|
+
var _036_attestations_exports = /* @__PURE__ */ __exportAll({
|
|
4193
|
+
down: () => down$6,
|
|
4194
|
+
up: () => up$6
|
|
4195
|
+
});
|
|
4196
|
+
/**
|
|
4197
|
+
* Add the `attestations` table for build-provenance bundles.
|
|
4198
|
+
*
|
|
4199
|
+
* When a workflow step calls `ctx.attestProvenance`, the agent uploads the
|
|
4200
|
+
* signed KiCI bundle to object storage under
|
|
4201
|
+
* `provenance/{run_id}/{job_id}/{subject_digest}.kici.json` and the
|
|
4202
|
+
* orchestrator records one row here so the dashboard can list and fetch
|
|
4203
|
+
* attestations per run/job.
|
|
4204
|
+
*
|
|
4205
|
+
* Idempotent: a re-run on a DB that already has the table is a no-op.
|
|
4206
|
+
*/
|
|
4207
|
+
async function up$6(db) {
|
|
4208
|
+
if ((await sql`
|
|
4209
|
+
SELECT EXISTS (
|
|
4210
|
+
SELECT 1 FROM information_schema.tables
|
|
4211
|
+
WHERE table_schema = 'public'
|
|
4212
|
+
AND table_name = 'attestations'
|
|
4213
|
+
) AS exists
|
|
4214
|
+
`.execute(db)).rows[0]?.exists) return;
|
|
4215
|
+
await sql`
|
|
4216
|
+
CREATE TABLE public.attestations (
|
|
4217
|
+
id TEXT PRIMARY KEY,
|
|
4218
|
+
run_id TEXT NOT NULL,
|
|
4219
|
+
job_id TEXT NOT NULL,
|
|
4220
|
+
subject_name TEXT NOT NULL,
|
|
4221
|
+
subject_digest TEXT NOT NULL,
|
|
4222
|
+
storage_key TEXT NOT NULL,
|
|
4223
|
+
mode TEXT NOT NULL,
|
|
4224
|
+
media_type TEXT NOT NULL,
|
|
4225
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
4226
|
+
)
|
|
4227
|
+
`.execute(db);
|
|
4228
|
+
await sql`
|
|
4229
|
+
CREATE INDEX idx_attestations_run_job
|
|
4230
|
+
ON public.attestations (run_id, job_id)
|
|
4231
|
+
`.execute(db);
|
|
4232
|
+
}
|
|
4233
|
+
async function down$6(db) {
|
|
4234
|
+
await sql`DROP TABLE IF EXISTS public.attestations`.execute(db);
|
|
4235
|
+
}
|
|
4236
|
+
//#endregion
|
|
4237
|
+
//#region src/db/migrations/037_generic_sources_provider_type_local.ts
|
|
4238
|
+
var _037_generic_sources_provider_type_local_exports = /* @__PURE__ */ __exportAll({
|
|
4239
|
+
down: () => down$5,
|
|
4240
|
+
up: () => up$5
|
|
4241
|
+
});
|
|
4242
|
+
/**
|
|
4243
|
+
* Replace the `generic_webhook_sources.provider_type` CHECK constraint so it
|
|
4244
|
+
* permits `'local'` instead of `'internal'`, and migrate any carried-over
|
|
4245
|
+
* `'internal'` rows to `'local'`.
|
|
4246
|
+
*
|
|
4247
|
+
* The local filesystem (`file://`) provider is `provider_type='local'`; the
|
|
4248
|
+
* value used to be `'internal'`. The original CHECK constraint
|
|
4249
|
+
* (`generic_webhook_sources_provider_type_check`) allowed only
|
|
4250
|
+
* `{'generic','internal'}`, so writing a `'local'` row fails. Universal-git
|
|
4251
|
+
* sources keep `provider_type='generic'` (discriminated by `git_config`), so
|
|
4252
|
+
* the new allowed set is `{'generic','local'}`.
|
|
4253
|
+
*
|
|
4254
|
+
* Idempotent: the constraint is dropped IF EXISTS and recreated; the data
|
|
4255
|
+
* backfill is a plain UPDATE that is a no-op once no `'internal'` rows remain.
|
|
4256
|
+
*/
|
|
4257
|
+
async function up$5(db) {
|
|
4258
|
+
await sql`
|
|
4259
|
+
ALTER TABLE public.generic_webhook_sources
|
|
4260
|
+
DROP CONSTRAINT IF EXISTS generic_webhook_sources_provider_type_check
|
|
4261
|
+
`.execute(db);
|
|
4262
|
+
await sql`
|
|
4263
|
+
UPDATE public.generic_webhook_sources
|
|
4264
|
+
SET provider_type = 'local'
|
|
4265
|
+
WHERE provider_type = 'internal'
|
|
4266
|
+
`.execute(db);
|
|
4267
|
+
await sql`
|
|
4268
|
+
ALTER TABLE public.generic_webhook_sources
|
|
4269
|
+
ADD CONSTRAINT generic_webhook_sources_provider_type_check
|
|
4270
|
+
CHECK (provider_type = ANY (ARRAY['generic'::text, 'local'::text]))
|
|
4271
|
+
`.execute(db);
|
|
4272
|
+
}
|
|
4273
|
+
async function down$5(db) {
|
|
4274
|
+
await sql`
|
|
4275
|
+
ALTER TABLE public.generic_webhook_sources
|
|
4276
|
+
DROP CONSTRAINT IF EXISTS generic_webhook_sources_provider_type_check
|
|
4277
|
+
`.execute(db);
|
|
4278
|
+
await sql`
|
|
4279
|
+
UPDATE public.generic_webhook_sources
|
|
4280
|
+
SET provider_type = 'internal'
|
|
4281
|
+
WHERE provider_type = 'local'
|
|
4282
|
+
`.execute(db);
|
|
4283
|
+
await sql`
|
|
4284
|
+
ALTER TABLE public.generic_webhook_sources
|
|
4285
|
+
ADD CONSTRAINT generic_webhook_sources_provider_type_check
|
|
4286
|
+
CHECK (provider_type = ANY (ARRAY['generic'::text, 'internal'::text]))
|
|
4287
|
+
`.execute(db);
|
|
4288
|
+
}
|
|
4289
|
+
//#endregion
|
|
4290
|
+
//#region src/db/migrations/038_remote_sources.ts
|
|
4291
|
+
var _038_remote_sources_exports = /* @__PURE__ */ __exportAll({
|
|
4292
|
+
down: () => down$4,
|
|
4293
|
+
up: () => up$4
|
|
4294
|
+
});
|
|
4295
|
+
/**
|
|
4296
|
+
* `remote_sources` anchors a Platform-relayed `kici run remote` to its real
|
|
4297
|
+
* org. One row per org served by this orchestrator: routing key
|
|
4298
|
+
* `remote:<orgId>` maps to the canonical org id, so `resolveOrgId` resolves
|
|
4299
|
+
* the real tenant through the same local-source path a webhook takes — no
|
|
4300
|
+
* GitHub App, no webhook secret, no manual setup. The row is auto-provisioned
|
|
4301
|
+
* on Platform auth from the canonical org id carried on `auth.success`.
|
|
4302
|
+
*
|
|
4303
|
+
* Idempotent: a re-run on a DB that already has the table is a no-op.
|
|
4304
|
+
*/
|
|
4305
|
+
async function up$4(db) {
|
|
4306
|
+
if ((await sql`
|
|
4307
|
+
SELECT EXISTS (
|
|
4308
|
+
SELECT 1 FROM information_schema.tables
|
|
4309
|
+
WHERE table_schema = 'public' AND table_name = 'remote_sources'
|
|
4310
|
+
) AS exists
|
|
4311
|
+
`.execute(db)).rows[0]?.exists) return;
|
|
4312
|
+
await sql`
|
|
4313
|
+
CREATE TABLE public.remote_sources (
|
|
4314
|
+
customer_id VARCHAR(255) NOT NULL,
|
|
4315
|
+
routing_key VARCHAR(255) NOT NULL,
|
|
4316
|
+
cluster_id TEXT,
|
|
4317
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
4318
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
4319
|
+
CONSTRAINT remote_sources_customer_id_key UNIQUE (customer_id),
|
|
4320
|
+
CONSTRAINT remote_sources_routing_key_key UNIQUE (routing_key)
|
|
4321
|
+
)
|
|
4322
|
+
`.execute(db);
|
|
4323
|
+
}
|
|
4324
|
+
async function down$4(db) {
|
|
4325
|
+
await sql`DROP TABLE IF EXISTS public.remote_sources`.execute(db);
|
|
4326
|
+
}
|
|
4327
|
+
//#endregion
|
|
4328
|
+
//#region src/db/migrations/039_host_roster.ts
|
|
4329
|
+
var _039_host_roster_exports = /* @__PURE__ */ __exportAll({
|
|
4330
|
+
down: () => down$3,
|
|
4331
|
+
up: () => up$3
|
|
4332
|
+
});
|
|
4333
|
+
/**
|
|
4334
|
+
* `host_roster` is KiCI's declared inventory: one durable row per agent the
|
|
4335
|
+
* cluster has ever enrolled, reconciled from the in-memory AgentRegistry on
|
|
4336
|
+
* every register/unregister. `lifecycle_class` (snapshot of the auth token's
|
|
4337
|
+
* agent_type) drives reaping — `ephemeral` rows are GC'd past their TTL,
|
|
4338
|
+
* `static` rows persist and read as `unreachable` when their heartbeat goes
|
|
4339
|
+
* stale. `connected_instance_id` records which orchestrator holds the live WS
|
|
4340
|
+
* (cluster liveness + the host-fanout reroute target); NULL = disconnected.
|
|
4341
|
+
*
|
|
4342
|
+
* The roster lives in the shared cluster DB (one table, all instances). Status
|
|
4343
|
+
* is derived at read from the shared `last_seen` + `connected_instance_id`, so
|
|
4344
|
+
* every instance agrees regardless of which one holds the agent's live WS.
|
|
4345
|
+
*
|
|
4346
|
+
* Idempotent: a re-run on a DB that already has the table is a no-op.
|
|
4347
|
+
*/
|
|
4348
|
+
async function up$3(db) {
|
|
4349
|
+
if ((await sql`
|
|
4350
|
+
SELECT EXISTS (
|
|
4351
|
+
SELECT 1 FROM information_schema.tables
|
|
4352
|
+
WHERE table_schema = 'public' AND table_name = 'host_roster'
|
|
4353
|
+
) AS exists
|
|
4354
|
+
`.execute(db)).rows[0]?.exists) return;
|
|
4355
|
+
await sql`
|
|
4356
|
+
CREATE TABLE public.host_roster (
|
|
4357
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
4358
|
+
agent_id text NOT NULL,
|
|
4359
|
+
token_id uuid,
|
|
4360
|
+
lifecycle_class text NOT NULL,
|
|
4361
|
+
labels text NOT NULL DEFAULT '[]',
|
|
4362
|
+
hostname text,
|
|
4363
|
+
platform text,
|
|
4364
|
+
arch text,
|
|
4365
|
+
connected_instance_id text,
|
|
4366
|
+
last_seen timestamptz NOT NULL DEFAULT now(),
|
|
4367
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
4368
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
4369
|
+
CONSTRAINT host_roster_pkey PRIMARY KEY (id),
|
|
4370
|
+
CONSTRAINT host_roster_agent_id_key UNIQUE (agent_id),
|
|
4371
|
+
CONSTRAINT host_roster_lifecycle_class_check
|
|
4372
|
+
CHECK (lifecycle_class = ANY (ARRAY['static'::text, 'ephemeral'::text]))
|
|
4373
|
+
)
|
|
4374
|
+
`.execute(db);
|
|
4375
|
+
await sql`CREATE INDEX idx_host_roster_reap
|
|
4376
|
+
ON public.host_roster (lifecycle_class, last_seen)`.execute(db);
|
|
4377
|
+
}
|
|
4378
|
+
async function down$3(db) {
|
|
4379
|
+
await sql`DROP TABLE IF EXISTS public.host_roster`.execute(db);
|
|
4380
|
+
}
|
|
4381
|
+
//#endregion
|
|
4382
|
+
//#region src/db/migrations/040_runsonall_pin.ts
|
|
4383
|
+
var _040_runsonall_pin_exports = /* @__PURE__ */ __exportAll({
|
|
4384
|
+
down: () => down$2,
|
|
4385
|
+
up: () => up$2
|
|
4386
|
+
});
|
|
4387
|
+
/**
|
|
4388
|
+
* Add the `runsOnAll` host fan-out columns:
|
|
4389
|
+
*
|
|
4390
|
+
* - `dispatch_queue.pinned_agent_id TEXT` — when set, the queued/dispatched job
|
|
4391
|
+
* targets exactly that agent (a host-fanout child). The dispatcher routes it
|
|
4392
|
+
* only to that agent; the queue drain never hands it to a different one.
|
|
4393
|
+
* - `execution_jobs.base_job_name` / `variant_kind` / `variant_label` — generic
|
|
4394
|
+
* fan-out columns (matrix + host uniform). `variant_kind` is `'matrix'` or
|
|
4395
|
+
* `'host'`; `variant_label` is the matrix suffix or the hostname. They make the
|
|
4396
|
+
* logical fan-out job first-class server-side so the dashboard groups on real
|
|
4397
|
+
* fields instead of string-parsing the job name. (`matrix_values` / `group_name`
|
|
4398
|
+
* already exist; the matrix path now also backfills `variant_kind='matrix'`.)
|
|
4399
|
+
*
|
|
4400
|
+
* Idempotent: re-running on a DB that already has any column is a no-op.
|
|
4401
|
+
*/
|
|
4402
|
+
async function colExists$2(db, table, name) {
|
|
4403
|
+
return (await sql`
|
|
4404
|
+
SELECT EXISTS (
|
|
4405
|
+
SELECT 1 FROM information_schema.columns
|
|
4406
|
+
WHERE table_schema = 'public'
|
|
4407
|
+
AND table_name = ${table}
|
|
4408
|
+
AND column_name = ${name}
|
|
4409
|
+
) AS exists
|
|
4410
|
+
`.execute(db)).rows[0]?.exists ?? false;
|
|
4411
|
+
}
|
|
4412
|
+
async function up$2(db) {
|
|
4413
|
+
if (!await colExists$2(db, "dispatch_queue", "pinned_agent_id")) await sql`ALTER TABLE public.dispatch_queue ADD COLUMN pinned_agent_id TEXT`.execute(db);
|
|
4414
|
+
if (!await colExists$2(db, "execution_jobs", "base_job_name")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN base_job_name TEXT`.execute(db);
|
|
4415
|
+
if (!await colExists$2(db, "execution_jobs", "variant_kind")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN variant_kind TEXT`.execute(db);
|
|
4416
|
+
if (!await colExists$2(db, "execution_jobs", "variant_label")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN variant_label TEXT`.execute(db);
|
|
4417
|
+
await sql`
|
|
4418
|
+
CREATE INDEX IF NOT EXISTS idx_dispatch_queue_pinned_agent
|
|
4419
|
+
ON public.dispatch_queue (pinned_agent_id)
|
|
4420
|
+
WHERE pinned_agent_id IS NOT NULL
|
|
4421
|
+
`.execute(db);
|
|
4422
|
+
}
|
|
4423
|
+
async function down$2(db) {
|
|
4424
|
+
await sql`DROP INDEX IF EXISTS public.idx_dispatch_queue_pinned_agent`.execute(db);
|
|
4425
|
+
await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS pinned_agent_id`.execute(db);
|
|
4426
|
+
await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS variant_label`.execute(db);
|
|
4427
|
+
await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS variant_kind`.execute(db);
|
|
4428
|
+
await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS base_job_name`.execute(db);
|
|
4429
|
+
}
|
|
4430
|
+
//#endregion
|
|
4431
|
+
//#region src/db/migrations/041_wave_gated.ts
|
|
4432
|
+
var _041_wave_gated_exports = /* @__PURE__ */ __exportAll({
|
|
4433
|
+
down: () => down$1,
|
|
4434
|
+
up: () => up$1
|
|
4435
|
+
});
|
|
4436
|
+
/**
|
|
4437
|
+
* Add the rolling fan-out wave-gate columns:
|
|
4438
|
+
*
|
|
4439
|
+
* - `execution_jobs.wave_gated boolean NOT NULL DEFAULT false` — when a fan-out
|
|
4440
|
+
* job declares `maxParallel`, children beyond the sliding window are persisted
|
|
4441
|
+
* `wave_gated=true` (held, not enqueued). The dispatch loop skips them; the
|
|
4442
|
+
* wave-scheduler clears the flag one-per-terminal as siblings complete (or, on
|
|
4443
|
+
* `failFast`, skips the held remainder).
|
|
4444
|
+
* - `execution_jobs.wave_max_parallel int` / `wave_fail_fast boolean` — the
|
|
4445
|
+
* base's wave policy, stamped on every fan-out child so the wave-scheduler can
|
|
4446
|
+
* read it at terminal time without re-fetching the lock file (the tracker has
|
|
4447
|
+
* no lock access). NULL for any job not part of a bounded wave.
|
|
4448
|
+
*
|
|
4449
|
+
* A composite index on (run_id, base_job_name, wave_gated) supports the
|
|
4450
|
+
* wave-scheduler's "next held sibling of this base" lookups.
|
|
4451
|
+
*
|
|
4452
|
+
* Idempotent: re-running on a DB that already has the columns is a no-op.
|
|
4453
|
+
*/
|
|
4454
|
+
async function colExists$1(db, table, name) {
|
|
4455
|
+
return (await sql`
|
|
4456
|
+
SELECT EXISTS (
|
|
4457
|
+
SELECT 1 FROM information_schema.columns
|
|
4458
|
+
WHERE table_schema = 'public'
|
|
4459
|
+
AND table_name = ${table}
|
|
4460
|
+
AND column_name = ${name}
|
|
4461
|
+
) AS exists
|
|
4462
|
+
`.execute(db)).rows[0]?.exists ?? false;
|
|
4463
|
+
}
|
|
4464
|
+
async function up$1(db) {
|
|
4465
|
+
if (!await colExists$1(db, "execution_jobs", "wave_gated")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN wave_gated boolean NOT NULL DEFAULT false`.execute(db);
|
|
4466
|
+
if (!await colExists$1(db, "execution_jobs", "wave_max_parallel")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN wave_max_parallel integer`.execute(db);
|
|
4467
|
+
if (!await colExists$1(db, "execution_jobs", "wave_fail_fast")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN wave_fail_fast boolean`.execute(db);
|
|
4468
|
+
await sql`
|
|
4469
|
+
CREATE INDEX IF NOT EXISTS idx_execution_jobs_wave
|
|
4470
|
+
ON public.execution_jobs (run_id, base_job_name, wave_gated)
|
|
4471
|
+
`.execute(db);
|
|
4472
|
+
}
|
|
4473
|
+
async function down$1(db) {
|
|
4474
|
+
await sql`DROP INDEX IF EXISTS public.idx_execution_jobs_wave`.execute(db);
|
|
4475
|
+
await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS wave_fail_fast`.execute(db);
|
|
4476
|
+
await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS wave_max_parallel`.execute(db);
|
|
4477
|
+
await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS wave_gated`.execute(db);
|
|
4478
|
+
}
|
|
4479
|
+
//#endregion
|
|
4480
|
+
//#region src/db/migrations/042_dispatch_queue_patterns.ts
|
|
4481
|
+
var _042_dispatch_queue_patterns_exports = /* @__PURE__ */ __exportAll({
|
|
4482
|
+
down: () => down,
|
|
4483
|
+
up: () => up
|
|
4484
|
+
});
|
|
4485
|
+
/**
|
|
4486
|
+
* Add pattern columns to dispatch_queue. Exact labels stay in runs_on_labels /
|
|
4487
|
+
* exclude_labels (the SQL @> prefilter); regex matchers go here and are applied
|
|
4488
|
+
* as a JS post-filter at drain time, since Postgres `~` regex semantics differ
|
|
4489
|
+
* from JavaScript `RegExp` and the engine's `matcherSatisfiedBy` is the single
|
|
4490
|
+
* matching authority.
|
|
4491
|
+
*
|
|
4492
|
+
* - `runs_on_patterns jsonb NOT NULL DEFAULT '[]'` — regex matchers the agent's
|
|
4493
|
+
* labels must satisfy.
|
|
4494
|
+
* - `exclude_patterns jsonb NOT NULL DEFAULT '[]'` — regex matchers that
|
|
4495
|
+
* disqualify an agent.
|
|
4496
|
+
*
|
|
4497
|
+
* Idempotent: re-running on a DB that already has the columns is a no-op.
|
|
4498
|
+
*/
|
|
4499
|
+
async function colExists(db, table, name) {
|
|
4500
|
+
return (await sql`
|
|
4501
|
+
SELECT EXISTS (
|
|
4502
|
+
SELECT 1 FROM information_schema.columns
|
|
4503
|
+
WHERE table_schema = 'public'
|
|
4504
|
+
AND table_name = ${table}
|
|
4505
|
+
AND column_name = ${name}
|
|
4506
|
+
) AS exists
|
|
4507
|
+
`.execute(db)).rows[0]?.exists ?? false;
|
|
4508
|
+
}
|
|
4509
|
+
async function up(db) {
|
|
4510
|
+
if (!await colExists(db, "dispatch_queue", "runs_on_patterns")) await sql`ALTER TABLE public.dispatch_queue ADD COLUMN runs_on_patterns jsonb NOT NULL DEFAULT '[]'::jsonb`.execute(db);
|
|
4511
|
+
if (!await colExists(db, "dispatch_queue", "exclude_patterns")) await sql`ALTER TABLE public.dispatch_queue ADD COLUMN exclude_patterns jsonb NOT NULL DEFAULT '[]'::jsonb`.execute(db);
|
|
4512
|
+
}
|
|
4513
|
+
async function down(db) {
|
|
4514
|
+
await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS exclude_patterns`.execute(db);
|
|
4515
|
+
await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS runs_on_patterns`.execute(db);
|
|
4516
|
+
}
|
|
4517
|
+
//#endregion
|
|
4159
4518
|
//#region src/db/migration-provider.ts
|
|
4160
4519
|
function createMigrationProvider() {
|
|
4161
4520
|
return { async getMigrations() {
|
|
@@ -4193,7 +4552,15 @@ function createMigrationProvider() {
|
|
|
4193
4552
|
"031_dispatch_queue_ack_deadline": _031_dispatch_queue_ack_deadline_exports,
|
|
4194
4553
|
"032_org_settings_dispatch_ack_timeout": _032_org_settings_dispatch_ack_timeout_exports,
|
|
4195
4554
|
"033_org_settings_approval": _033_org_settings_approval_exports,
|
|
4196
|
-
"034_held_runs_generalize": _034_held_runs_generalize_exports
|
|
4555
|
+
"034_held_runs_generalize": _034_held_runs_generalize_exports,
|
|
4556
|
+
"035_pending_workflow_contexts": _035_pending_workflow_contexts_exports,
|
|
4557
|
+
"036_attestations": _036_attestations_exports,
|
|
4558
|
+
"037_generic_sources_provider_type_local": _037_generic_sources_provider_type_local_exports,
|
|
4559
|
+
"038_remote_sources": _038_remote_sources_exports,
|
|
4560
|
+
"039_host_roster": _039_host_roster_exports,
|
|
4561
|
+
"040_runsonall_pin": _040_runsonall_pin_exports,
|
|
4562
|
+
"041_wave_gated": _041_wave_gated_exports,
|
|
4563
|
+
"042_dispatch_queue_patterns": _042_dispatch_queue_patterns_exports
|
|
4197
4564
|
};
|
|
4198
4565
|
} };
|
|
4199
4566
|
}
|
|
@@ -4260,7 +4627,7 @@ async function runMigrations(opts) {
|
|
|
4260
4627
|
* as `kici-admin peer create-token` — open a pool from KICI_DATABASE_URL /
|
|
4261
4628
|
* DATABASE_URL / --database-url.
|
|
4262
4629
|
*/
|
|
4263
|
-
function resolveDatabaseUrl$
|
|
4630
|
+
function resolveDatabaseUrl$2(explicit) {
|
|
4264
4631
|
const url = explicit ?? process.env.KICI_DATABASE_URL;
|
|
4265
4632
|
if (!url) throw new Error("Database URL required. Pass --database-url or set KICI_DATABASE_URL.");
|
|
4266
4633
|
return url;
|
|
@@ -4307,7 +4674,7 @@ function registerDbCommands(program, getClient) {
|
|
|
4307
4674
|
});
|
|
4308
4675
|
db.command("fresh").description("DROP + CREATE the orchestrator DB, run migrations, record content hash").option("--database-url <url>", "Target database URL (else KICI_DATABASE_URL / DATABASE_URL)").requiredOption("--confirm", "Explicit confirmation (destructive)").option("--yes", "Skip interactive confirmation (for scripted use)").action(async (opts) => {
|
|
4309
4676
|
try {
|
|
4310
|
-
const url = resolveDatabaseUrl$
|
|
4677
|
+
const url = resolveDatabaseUrl$2(opts.databaseUrl);
|
|
4311
4678
|
const { dbName } = parseDatabaseUrl(url);
|
|
4312
4679
|
if (!opts.yes) {
|
|
4313
4680
|
if (!await confirmInteractive$1(`About to DROP + RECREATE database "${dbName}". Type "${dbName}" to confirm: `, dbName)) {
|
|
@@ -4338,7 +4705,7 @@ function registerDbCommands(program, getClient) {
|
|
|
4338
4705
|
});
|
|
4339
4706
|
db.command("ensure <name>").description("CREATE DATABASE IF NOT EXISTS (idempotent)").option("--database-url <url>", "Admin DB URL (else KICI_DATABASE_URL / DATABASE_URL)").option("--owner <role>", "DB owner role (default: URL user). Pass when the admin connection is privileged but the new DB should be owned by a separate non-privileged role.").option("--revoke-connect-public", "After ensure, REVOKE CONNECT ON DATABASE \"<name>\" FROM PUBLIC (recommended on shared clusters).").option("--grant-connect-role <role>", "After ensure (and any --revoke-connect-public), GRANT CONNECT ON DATABASE \"<name>\" TO \"<role>\". Repeatable.", (val, acc) => acc.concat([val]), []).action(async (name, opts) => {
|
|
4340
4707
|
try {
|
|
4341
|
-
const baseUrl = resolveDatabaseUrl$
|
|
4708
|
+
const baseUrl = resolveDatabaseUrl$2(opts.databaseUrl);
|
|
4342
4709
|
const url = new URL(baseUrl);
|
|
4343
4710
|
url.pathname = `/${name}`;
|
|
4344
4711
|
const targetUrl = url.toString();
|
|
@@ -4357,7 +4724,7 @@ function registerDbCommands(program, getClient) {
|
|
|
4357
4724
|
});
|
|
4358
4725
|
db.command("create-role").description("CREATE / ALTER ROLE with LOGIN [+ CREATEDB] (idempotent)").option("--database-url <url>", "Admin DB URL (else KICI_DATABASE_URL / DATABASE_URL)").requiredOption("--user <name>", "Role name to create or update").requiredOption("--password <password>", "Role password (raw — quote as needed)").option("--createdb", "Grant CREATEDB to the new role", false).action(async (opts) => {
|
|
4359
4726
|
try {
|
|
4360
|
-
const { adminUrl } = parseDatabaseUrl(resolveDatabaseUrl$
|
|
4727
|
+
const { adminUrl } = parseDatabaseUrl(resolveDatabaseUrl$2(opts.databaseUrl));
|
|
4361
4728
|
logInvocation(`create-role ${opts.user}`, adminUrl);
|
|
4362
4729
|
const outcome = await createDbRole(adminUrl, {
|
|
4363
4730
|
username: opts.user,
|
|
@@ -4372,7 +4739,7 @@ function registerDbCommands(program, getClient) {
|
|
|
4372
4739
|
});
|
|
4373
4740
|
db.command("create-readonly-user").description("Create a read-only role with SELECT on all tables + default privileges").option("--database-url <url>", "Target DB URL (must connect as owner)").requiredOption("--user <name>", "Read-only role name").requiredOption("--password <password>", "Role password").action(async (opts) => {
|
|
4374
4741
|
try {
|
|
4375
|
-
const url = resolveDatabaseUrl$
|
|
4742
|
+
const url = resolveDatabaseUrl$2(opts.databaseUrl);
|
|
4376
4743
|
const { dbName } = parseDatabaseUrl(url);
|
|
4377
4744
|
logInvocation(`create-readonly-user ${opts.user}`, url);
|
|
4378
4745
|
const outcome = await createReadOnlyDbUser(url, {
|
|
@@ -4387,7 +4754,7 @@ function registerDbCommands(program, getClient) {
|
|
|
4387
4754
|
});
|
|
4388
4755
|
db.command("check-schema").description("Compare bundled migrations vs live schema. Exit 2 on drift.").option("--database-url <url>", "Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)").option("--json", "Emit JSON instead of a human-readable line", false).action(async (opts) => {
|
|
4389
4756
|
try {
|
|
4390
|
-
const pool = createPool(resolveDatabaseUrl$
|
|
4757
|
+
const pool = createPool(resolveDatabaseUrl$2(opts.databaseUrl));
|
|
4391
4758
|
try {
|
|
4392
4759
|
const status = await isSchemaCurrent(pool, createMigrationProvider());
|
|
4393
4760
|
if (opts.json) process.stdout.write(JSON.stringify(status) + "\n");
|
|
@@ -4404,7 +4771,7 @@ function registerDbCommands(program, getClient) {
|
|
|
4404
4771
|
});
|
|
4405
4772
|
db.command("collation-check").description("Compare pg_database.datcollversion against the running libc collation version. Exit 2 on drift.").option("--database-url <url>", "Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)").option("--json", "Emit JSON instead of a human-readable line", false).action(async (opts) => {
|
|
4406
4773
|
try {
|
|
4407
|
-
const url = resolveDatabaseUrl$
|
|
4774
|
+
const url = resolveDatabaseUrl$2(opts.databaseUrl);
|
|
4408
4775
|
const { dbName } = parseDatabaseUrl(url);
|
|
4409
4776
|
const pool = createPool(url);
|
|
4410
4777
|
try {
|
|
@@ -4434,7 +4801,7 @@ function registerDbCommands(program, getClient) {
|
|
|
4434
4801
|
});
|
|
4435
4802
|
db.command("reindex").description("REINDEX DATABASE CONCURRENTLY <db>. Rebuilds every index under the running libc collation rules. Non-blocking but takes minutes + ~2× temp disk.").option("--database-url <url>", "Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)").requiredOption("--confirm", "Explicit confirmation (destructive — long-running)").requiredOption("--reason <text>", "Reason (recorded in stderr banner)").action(async (opts) => {
|
|
4436
4803
|
try {
|
|
4437
|
-
const url = resolveDatabaseUrl$
|
|
4804
|
+
const url = resolveDatabaseUrl$2(opts.databaseUrl);
|
|
4438
4805
|
const { dbName } = parseDatabaseUrl(url);
|
|
4439
4806
|
logInvocation(`reindex ${dbName} (${opts.reason})`, url);
|
|
4440
4807
|
const pool = createPool(url);
|
|
@@ -4451,7 +4818,7 @@ function registerDbCommands(program, getClient) {
|
|
|
4451
4818
|
});
|
|
4452
4819
|
db.command("refresh-collation-version").description("ALTER DATABASE <db> REFRESH COLLATION VERSION. Metadata-only bump; pair with db reindex after a libc-base image rebuild.").option("--database-url <url>", "Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)").requiredOption("--reason <text>", "Reason (recorded in stderr banner)").action(async (opts) => {
|
|
4453
4820
|
try {
|
|
4454
|
-
const url = resolveDatabaseUrl$
|
|
4821
|
+
const url = resolveDatabaseUrl$2(opts.databaseUrl);
|
|
4455
4822
|
const { dbName } = parseDatabaseUrl(url);
|
|
4456
4823
|
logInvocation(`refresh-collation-version ${dbName} (${opts.reason})`, url);
|
|
4457
4824
|
const pool = createPool(url);
|
|
@@ -4707,6 +5074,119 @@ promisify(execFile);
|
|
|
4707
5074
|
createLogger({ prefix: "universal-git:lock-file" });
|
|
4708
5075
|
createLogger({ prefix: "universal-git:clone-token" });
|
|
4709
5076
|
//#endregion
|
|
5077
|
+
//#region src/providers/local/local-source-config.ts
|
|
5078
|
+
/**
|
|
5079
|
+
* Configuration for a local filesystem (`file://`) source, stored as JSONB in
|
|
5080
|
+
* `generic_webhook_sources.git_config` and discriminated from universal-git
|
|
5081
|
+
* config by the row's `provider_type='local'`.
|
|
5082
|
+
*
|
|
5083
|
+
* A local source clones a git repository that already exists on the agent's
|
|
5084
|
+
* filesystem (host path for bare-metal scalers, image-bundled / rootfs /
|
|
5085
|
+
* bind-mounted path for container + Firecracker scalers). The orchestrator does
|
|
5086
|
+
* not verify in-agent reachability — that is the operator's responsibility.
|
|
5087
|
+
*/
|
|
5088
|
+
const LocalSourceConfigSchema = z.object({
|
|
5089
|
+
/** Absolute path to the repo (or base dir of repos) on the agent filesystem. */
|
|
5090
|
+
repoBasePath: z.string().min(1).refine((p) => path.isAbsolute(p), { message: "repoBasePath must be an absolute path" }),
|
|
5091
|
+
/** Optional network clone base (git:// / http://) for remote agents that do
|
|
5092
|
+
* not share the orchestrator's filesystem. When unset, file:// is used. */
|
|
5093
|
+
cloneUrlBase: z.string().min(1).optional()
|
|
5094
|
+
}).strict();
|
|
5095
|
+
//#endregion
|
|
5096
|
+
//#region src/cli/commands/local-trigger.ts
|
|
5097
|
+
/**
|
|
5098
|
+
* Build and send the synthetic webhook that triggers a run against a local
|
|
5099
|
+
* filesystem (`file://`) source. The orchestrator's provider-agnostic generic
|
|
5100
|
+
* webhook route (`POST /webhook/:orgId/generic/:sourceId`) dispatches it through
|
|
5101
|
+
* the local provider's normalizer. Shared by the `source trigger-local` CLI
|
|
5102
|
+
* command and the generated `post-receive` hook.
|
|
5103
|
+
*
|
|
5104
|
+
* A local repo has no remote forge to send webhooks, so this is the operator's
|
|
5105
|
+
* way to drive a run: either by hand (`trigger-local`) or on every push via an
|
|
5106
|
+
* installed `post-receive` hook (`install-hook`).
|
|
5107
|
+
*/
|
|
5108
|
+
/** Build the GitHub-shaped webhook request the local provider normalizer expects. */
|
|
5109
|
+
function buildLocalTriggerRequest(input) {
|
|
5110
|
+
const body = JSON.stringify({
|
|
5111
|
+
ref: input.ref,
|
|
5112
|
+
after: input.sha,
|
|
5113
|
+
repository: {
|
|
5114
|
+
full_name: input.repoFullName,
|
|
5115
|
+
default_branch: input.defaultBranch
|
|
5116
|
+
}
|
|
5117
|
+
});
|
|
5118
|
+
return {
|
|
5119
|
+
path: `/webhook/${input.orgId}/generic/${input.sourceId}`,
|
|
5120
|
+
headers: {
|
|
5121
|
+
"content-type": "application/json",
|
|
5122
|
+
"x-event-type": input.event,
|
|
5123
|
+
"x-delivery-id": randomUUID()
|
|
5124
|
+
},
|
|
5125
|
+
body
|
|
5126
|
+
};
|
|
5127
|
+
}
|
|
5128
|
+
/** Read HEAD ref + sha from a local git repo (used when the operator omits --ref/--sha). */
|
|
5129
|
+
function readRepoHead(repoPath) {
|
|
5130
|
+
const sha = execFileSync("git", [
|
|
5131
|
+
"-C",
|
|
5132
|
+
repoPath,
|
|
5133
|
+
"rev-parse",
|
|
5134
|
+
"HEAD"
|
|
5135
|
+
], { encoding: "utf8" }).trim();
|
|
5136
|
+
return {
|
|
5137
|
+
ref: `refs/heads/${execFileSync("git", [
|
|
5138
|
+
"-C",
|
|
5139
|
+
repoPath,
|
|
5140
|
+
"rev-parse",
|
|
5141
|
+
"--abbrev-ref",
|
|
5142
|
+
"HEAD"
|
|
5143
|
+
], { encoding: "utf8" }).trim()}`,
|
|
5144
|
+
sha
|
|
5145
|
+
};
|
|
5146
|
+
}
|
|
5147
|
+
/** POST the trigger request to the orchestrator base URL. Returns the HTTP status. */
|
|
5148
|
+
async function sendLocalTrigger(baseUrl, req) {
|
|
5149
|
+
return (await fetch(`${baseUrl.replace(/\/$/, "")}${req.path}`, {
|
|
5150
|
+
method: "POST",
|
|
5151
|
+
headers: req.headers,
|
|
5152
|
+
body: req.body
|
|
5153
|
+
})).status;
|
|
5154
|
+
}
|
|
5155
|
+
//#endregion
|
|
5156
|
+
//#region src/cli/commands/local-hook.ts
|
|
5157
|
+
/**
|
|
5158
|
+
* Render + install a git `post-receive` hook that triggers a KiCI run on every
|
|
5159
|
+
* push to a local filesystem (`file://`) source. The hook is a thin wrapper
|
|
5160
|
+
* around `kici-admin source trigger-local`; it carries no logic of its own.
|
|
5161
|
+
*/
|
|
5162
|
+
/**
|
|
5163
|
+
* Render the post-receive hook script. For each pushed ref the hook calls
|
|
5164
|
+
* `kici-admin source trigger-local <id> --ref <ref> --sha <new> --base-url <url>`,
|
|
5165
|
+
* so a push to any branch dispatches a run for that ref.
|
|
5166
|
+
*/
|
|
5167
|
+
function renderPostReceiveHook(opts) {
|
|
5168
|
+
return `#!/usr/bin/env bash
|
|
5169
|
+
# KiCI local-source post-receive hook (generated by 'kici-admin source install-hook').
|
|
5170
|
+
# Triggers a run for each pushed branch ref.
|
|
5171
|
+
set -euo pipefail
|
|
5172
|
+
while read -r _old _new ref; do
|
|
5173
|
+
kici-admin source trigger-local ${opts.sourceId} --ref "$ref" --sha "$_new" --base-url ${opts.baseUrl}
|
|
5174
|
+
done
|
|
5175
|
+
`;
|
|
5176
|
+
}
|
|
5177
|
+
/**
|
|
5178
|
+
* Write the rendered hook into the repo's `.git/hooks/post-receive` and make it
|
|
5179
|
+
* executable. Throws when the path is not a git repo (no `.git/hooks` dir).
|
|
5180
|
+
*/
|
|
5181
|
+
function installPostReceiveHook(repoPath, script) {
|
|
5182
|
+
const hooksDir = path.join(repoPath, ".git", "hooks");
|
|
5183
|
+
if (!existsSync(hooksDir)) throw new Error(`Not a git repo (no .git/hooks): ${repoPath}`);
|
|
5184
|
+
const hookPath = path.join(hooksDir, "post-receive");
|
|
5185
|
+
writeFileSync(hookPath, script, { mode: 493 });
|
|
5186
|
+
chmodSync(hookPath, 493);
|
|
5187
|
+
return hookPath;
|
|
5188
|
+
}
|
|
5189
|
+
//#endregion
|
|
4710
5190
|
//#region src/cli/commands/source.ts
|
|
4711
5191
|
/**
|
|
4712
5192
|
* Source management commands for kici-admin.
|
|
@@ -4728,6 +5208,24 @@ createLogger({ prefix: "universal-git:clone-token" });
|
|
|
4728
5208
|
* --from-env <VAR_NAME> Read from environment variable
|
|
4729
5209
|
* --stdin Read from stdin
|
|
4730
5210
|
*/
|
|
5211
|
+
/** Default orchestrator base URL for the webhook trigger route, matching the
|
|
5212
|
+
* kici-admin global `--url` default. */
|
|
5213
|
+
const DEFAULT_ORCH_URL = process.env.KICI_ADMIN_URL ?? "http://localhost:8080";
|
|
5214
|
+
/** Parse a generic source's git_config (string or object) into a LocalSourceConfig.
|
|
5215
|
+
* Returns null when the row is not a valid local source. */
|
|
5216
|
+
function parseLocalConfig(s) {
|
|
5217
|
+
if (s.provider_type !== "local" || !s.git_config) return null;
|
|
5218
|
+
const raw = typeof s.git_config === "string" ? safeParse(s.git_config) : s.git_config;
|
|
5219
|
+
const parsed = LocalSourceConfigSchema.safeParse(raw);
|
|
5220
|
+
return parsed.success ? parsed.data : null;
|
|
5221
|
+
}
|
|
5222
|
+
function safeParse(value) {
|
|
5223
|
+
try {
|
|
5224
|
+
return JSON.parse(value);
|
|
5225
|
+
} catch {
|
|
5226
|
+
return null;
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
4731
5229
|
/**
|
|
4732
5230
|
* Short reason shown in `(unavailable — <reason>)` when no webhook URL could be
|
|
4733
5231
|
* resolved for an added source.
|
|
@@ -4809,7 +5307,11 @@ function formatGenericSource(s) {
|
|
|
4809
5307
|
} catch {
|
|
4810
5308
|
gc = null;
|
|
4811
5309
|
}
|
|
4812
|
-
if (gc) {
|
|
5310
|
+
if (gc && s.provider_type === "local") {
|
|
5311
|
+
console.log(` Local (file://): yes`);
|
|
5312
|
+
if (typeof gc.repoBasePath === "string") console.log(` Repo base path: ${gc.repoBasePath}`);
|
|
5313
|
+
if (typeof gc.cloneUrlBase === "string") console.log(` Clone URL base: ${gc.cloneUrlBase}`);
|
|
5314
|
+
} else if (gc) {
|
|
4813
5315
|
console.log(` Universal-git: yes`);
|
|
4814
5316
|
if (typeof gc.preset === "string") console.log(` Preset: ${gc.preset}`);
|
|
4815
5317
|
if (typeof gc.gitUrlTemplate === "string") console.log(` Git URL template: ${gc.gitUrlTemplate}`);
|
|
@@ -4907,7 +5409,7 @@ function registerSourceCommands(program, getClient) {
|
|
|
4907
5409
|
process.exit(1);
|
|
4908
5410
|
}
|
|
4909
5411
|
});
|
|
4910
|
-
add.command("generic").description("Add a new generic webhook source").requiredOption("--org <orgId>", "Organization/customer ID").requiredOption("--name <name>", "Human-readable source name").option("--verification <method>", "Verification method: hmac_sha256, bearer_token, ip_allowlist, none").option("--secret <value>", "Verification secret (HMAC secret or bearer token, prefix with @ for file)").option("--from-env <varName>", "Read verification secret from environment variable").option("--stdin", "Read verification secret from stdin").option("--event-type-header <header>", "Header name for event type extraction").option("--event-type-path <jsonpath>", "JSONPath for event type extraction from body").option("--idempotency-key-header <header>", "Header name for idempotency key").option("--idempotency-key-path <jsonpath>", "JSONPath for idempotency key from body").option("--dedup-window <seconds>", "Dedup window in seconds (default: 300)", parseInt).option("--max-payload <bytes>", "Maximum payload size in bytes (default: 1048576)", parseInt).option("--allowed-events <events>", "Comma-separated list of allowed event types").option("--strip-headers <headers>", "Comma-separated list of headers to strip").option("--rate-limit <rpm>", "Rate limit in requests per minute (default: 600)", parseInt).option("--preset <name>", "Universal-git preset: forgejo, gitea, gogs, gitlab-repo, github-repo, custom").option("--git-url-template <url>", "Clone URL template with {owner}/{name}/{repo}").option("--credential-ref <key>", "Secret key name (under __source__/<id> scope)").option("--credential-store <backend>", "Secret backend name (default: pg)").option("--credential-type <type>", "Credential type: pat, basic, ssh").option("--credential-user <user>", "Username for PAT/basic auth (default: x-access-token)").option("--ssh-host-key-policy <policy>", "SSH host-key policy: accept-new, pinned").option("--ssh-known-hosts-pem <pathOrValue>", "Pinned SSH known_hosts (prefix with @ for file). Required when --ssh-host-key-policy=pinned").option("--provider-type <type>", "Provider implementation: generic (default) or
|
|
5412
|
+
add.command("generic").description("Add a new generic webhook source").requiredOption("--org <orgId>", "Organization/customer ID").requiredOption("--name <name>", "Human-readable source name").option("--verification <method>", "Verification method: hmac_sha256, bearer_token, ip_allowlist, none").option("--secret <value>", "Verification secret (HMAC secret or bearer token, prefix with @ for file)").option("--from-env <varName>", "Read verification secret from environment variable").option("--stdin", "Read verification secret from stdin").option("--event-type-header <header>", "Header name for event type extraction").option("--event-type-path <jsonpath>", "JSONPath for event type extraction from body").option("--idempotency-key-header <header>", "Header name for idempotency key").option("--idempotency-key-path <jsonpath>", "JSONPath for idempotency key from body").option("--dedup-window <seconds>", "Dedup window in seconds (default: 300)", parseInt).option("--max-payload <bytes>", "Maximum payload size in bytes (default: 1048576)", parseInt).option("--allowed-events <events>", "Comma-separated list of allowed event types").option("--strip-headers <headers>", "Comma-separated list of headers to strip").option("--rate-limit <rpm>", "Rate limit in requests per minute (default: 600)", parseInt).option("--preset <name>", "Universal-git preset: forgejo, gitea, gogs, gitlab-repo, github-repo, custom").option("--git-url-template <url>", "Clone URL template with {owner}/{name}/{repo}").option("--credential-ref <key>", "Secret key name (under __source__/<id> scope)").option("--credential-store <backend>", "Secret backend name (default: pg)").option("--credential-type <type>", "Credential type: pat, basic, ssh").option("--credential-user <user>", "Username for PAT/basic auth (default: x-access-token)").option("--ssh-host-key-policy <policy>", "SSH host-key policy: accept-new, pinned").option("--ssh-known-hosts-pem <pathOrValue>", "Pinned SSH known_hosts (prefix with @ for file). Required when --ssh-host-key-policy=pinned").option("--provider-type <type>", "Provider implementation: generic (default) or local (a git repo on the agent filesystem cloned via file://)").option("--json", "Emit raw JSON (the full source row) instead of formatted text").action(async (opts) => {
|
|
4911
5413
|
try {
|
|
4912
5414
|
const secret = await resolveSecret({
|
|
4913
5415
|
value: opts.secret,
|
|
@@ -4959,6 +5461,39 @@ function registerSourceCommands(program, getClient) {
|
|
|
4959
5461
|
process.exit(1);
|
|
4960
5462
|
}
|
|
4961
5463
|
});
|
|
5464
|
+
add.command("local").description("Register a git repo present on the agent filesystem as a file:// source").requiredOption("--org <orgId>", "Organization/customer ID").requiredOption("--path <dir>", "Absolute path to the repo (or base dir of repos) on the agent filesystem").option("--name <name>", "Human-readable source name", "local").option("--clone-url-base <url>", "Optional git://|http:// base for remote agents that do not share the orchestrator filesystem (default: file://)").option("--json", "Emit raw JSON (the full source row) instead of formatted text").action(async (opts) => {
|
|
5465
|
+
try {
|
|
5466
|
+
if (!path.isAbsolute(opts.path)) {
|
|
5467
|
+
console.error(`Error: --path must be an absolute path: ${opts.path}`);
|
|
5468
|
+
process.exit(1);
|
|
5469
|
+
}
|
|
5470
|
+
const localConfig = { repoBasePath: opts.path };
|
|
5471
|
+
if (opts.cloneUrlBase) localConfig.cloneUrlBase = opts.cloneUrlBase;
|
|
5472
|
+
const s = (await getClient().createGenericSource({
|
|
5473
|
+
orgId: opts.org,
|
|
5474
|
+
name: opts.name,
|
|
5475
|
+
providerType: "local",
|
|
5476
|
+
verificationMethod: "none",
|
|
5477
|
+
localConfig
|
|
5478
|
+
})).source;
|
|
5479
|
+
if (opts.json) {
|
|
5480
|
+
console.log(JSON.stringify(s, null, 2));
|
|
5481
|
+
return;
|
|
5482
|
+
}
|
|
5483
|
+
console.log(`Local source created:`);
|
|
5484
|
+
console.log(` ID: ${s.id}`);
|
|
5485
|
+
console.log(` Name: ${s.name}`);
|
|
5486
|
+
console.log(` Routing key: ${s.routing_key}`);
|
|
5487
|
+
console.log(` Org: ${s.customer_id}`);
|
|
5488
|
+
console.log(` Repo path: ${opts.path}`);
|
|
5489
|
+
console.log("");
|
|
5490
|
+
console.log(`Trigger runs with: kici-admin source trigger-local ${s.id}`);
|
|
5491
|
+
console.log(`Or install a push hook: kici-admin source install-hook ${s.id}`);
|
|
5492
|
+
} catch (err) {
|
|
5493
|
+
console.error(`Error: ${toErrorMessage(err)}`);
|
|
5494
|
+
process.exit(1);
|
|
5495
|
+
}
|
|
5496
|
+
});
|
|
4962
5497
|
src.command("list-presets").description("List built-in universal-git presets (forge shapes supported out of the box)").option("--format <format>", "Output format: table|json", "table").action((opts) => {
|
|
4963
5498
|
const rows = Object.entries(UNIVERSAL_GIT_PRESETS).map(([name, def]) => ({
|
|
4964
5499
|
preset: name,
|
|
@@ -5070,7 +5605,7 @@ function registerSourceCommands(program, getClient) {
|
|
|
5070
5605
|
process.exit(1);
|
|
5071
5606
|
}
|
|
5072
5607
|
});
|
|
5073
|
-
src.command("update-generic <id>").description("Update a generic webhook source").option("--name <name>", "New name").option("--verification <method>", "Verification method: hmac_sha256, bearer_token, ip_allowlist, none").option("--secret <value>", "New verification secret (prefix with @ for file)").option("--from-env <varName>", "Read verification secret from environment variable").option("--stdin", "Read verification secret from stdin").option("--event-type-header <header>", "Header name for event type extraction").option("--event-type-path <jsonpath>", "JSONPath for event type extraction from body").option("--idempotency-key-header <header>", "Header name for idempotency key").option("--idempotency-key-path <jsonpath>", "JSONPath for idempotency key from body").option("--dedup-window <seconds>", "Dedup window in seconds", parseInt).option("--max-payload <bytes>", "Maximum payload size in bytes", parseInt).option("--allowed-events <events>", "Comma-separated list of allowed event types").option("--strip-headers <headers>", "Comma-separated list of headers to strip").option("--rate-limit <rpm>", "Rate limit in requests per minute", parseInt).option("--preset <name>", "Universal-git preset").option("--git-url-template <url>", "Clone URL template").option("--credential-ref <key>", "Secret key name").option("--credential-store <backend>", "Secret backend name").option("--credential-type <type>", "Credential type: pat, basic, ssh").option("--credential-user <user>", "Username for PAT/basic auth").option("--ssh-host-key-policy <policy>", "SSH host-key policy: accept-new, pinned").option("--ssh-known-hosts-pem <pathOrValue>", "Pinned SSH known_hosts (prefix with @ for file)").option("--clear-git-config", "Revert this source back to a payload-only generic webhook").option("--provider-type <type>", "Provider implementation: generic (default) or
|
|
5608
|
+
src.command("update-generic <id>").description("Update a generic webhook source").option("--name <name>", "New name").option("--verification <method>", "Verification method: hmac_sha256, bearer_token, ip_allowlist, none").option("--secret <value>", "New verification secret (prefix with @ for file)").option("--from-env <varName>", "Read verification secret from environment variable").option("--stdin", "Read verification secret from stdin").option("--event-type-header <header>", "Header name for event type extraction").option("--event-type-path <jsonpath>", "JSONPath for event type extraction from body").option("--idempotency-key-header <header>", "Header name for idempotency key").option("--idempotency-key-path <jsonpath>", "JSONPath for idempotency key from body").option("--dedup-window <seconds>", "Dedup window in seconds", parseInt).option("--max-payload <bytes>", "Maximum payload size in bytes", parseInt).option("--allowed-events <events>", "Comma-separated list of allowed event types").option("--strip-headers <headers>", "Comma-separated list of headers to strip").option("--rate-limit <rpm>", "Rate limit in requests per minute", parseInt).option("--preset <name>", "Universal-git preset").option("--git-url-template <url>", "Clone URL template").option("--credential-ref <key>", "Secret key name").option("--credential-store <backend>", "Secret backend name").option("--credential-type <type>", "Credential type: pat, basic, ssh").option("--credential-user <user>", "Username for PAT/basic auth").option("--ssh-host-key-policy <policy>", "SSH host-key policy: accept-new, pinned").option("--ssh-known-hosts-pem <pathOrValue>", "Pinned SSH known_hosts (prefix with @ for file)").option("--clear-git-config", "Revert this source back to a payload-only generic webhook").option("--provider-type <type>", "Provider implementation: generic (default) or local (a git repo on the agent filesystem cloned via file://)").option("--json", "Emit raw JSON (the full source row) instead of formatted text").action(async (id, opts) => {
|
|
5074
5609
|
try {
|
|
5075
5610
|
const secret = await resolveSecret({
|
|
5076
5611
|
value: opts.secret,
|
|
@@ -5117,7 +5652,32 @@ function registerSourceCommands(program, getClient) {
|
|
|
5117
5652
|
process.exit(1);
|
|
5118
5653
|
}
|
|
5119
5654
|
});
|
|
5120
|
-
src.command("
|
|
5655
|
+
src.command("update-local <id>").description("Update a local filesystem (file://) source").option("--name <name>", "New name").option("--path <dir>", "New absolute repo base path on the agent filesystem").option("--clone-url-base <url>", "New git://|http:// clone base (default: file://)").option("--json", "Emit raw JSON (the full source row) instead of formatted text").action(async (id, opts) => {
|
|
5656
|
+
try {
|
|
5657
|
+
const data = {};
|
|
5658
|
+
if (opts.name) data.name = opts.name;
|
|
5659
|
+
if (opts.path !== void 0) {
|
|
5660
|
+
if (!path.isAbsolute(opts.path)) {
|
|
5661
|
+
console.error(`Error: --path must be an absolute path: ${opts.path}`);
|
|
5662
|
+
process.exit(1);
|
|
5663
|
+
}
|
|
5664
|
+
const localConfig = { repoBasePath: opts.path };
|
|
5665
|
+
if (opts.cloneUrlBase) localConfig.cloneUrlBase = opts.cloneUrlBase;
|
|
5666
|
+
data.localConfig = localConfig;
|
|
5667
|
+
}
|
|
5668
|
+
if (Object.keys(data).length === 0) {
|
|
5669
|
+
console.error("Error: no fields to update. Provide --path and/or --name.");
|
|
5670
|
+
process.exit(1);
|
|
5671
|
+
}
|
|
5672
|
+
const result = await getClient().updateGenericSource(id, data);
|
|
5673
|
+
if (opts.json) console.log(JSON.stringify(result.source, null, 2));
|
|
5674
|
+
else console.log(`Local source updated: ${result.source.id} (${result.source.name})`);
|
|
5675
|
+
} catch (err) {
|
|
5676
|
+
console.error(`Error: ${toErrorMessage(err)}`);
|
|
5677
|
+
process.exit(1);
|
|
5678
|
+
}
|
|
5679
|
+
});
|
|
5680
|
+
src.command("remove <routingKey>").description("Remove a source (GitHub, or generic/local with --generic / --local)").option("--yes", "Skip confirmation").option("--generic", "Remove a generic source (routingKey is treated as source ID)").option("--local", "Remove a local (file://) source (routingKey is treated as source ID)").option("--hard", "Permanently delete a generic/local source (requires --generic or --local)").action(async (routingKey, opts) => {
|
|
5121
5681
|
try {
|
|
5122
5682
|
if (!opts.yes) {
|
|
5123
5683
|
const rl = createInterface({
|
|
@@ -5133,12 +5693,13 @@ function registerSourceCommands(program, getClient) {
|
|
|
5133
5693
|
return;
|
|
5134
5694
|
}
|
|
5135
5695
|
}
|
|
5136
|
-
if (opts.generic) {
|
|
5696
|
+
if (opts.generic || opts.local) {
|
|
5137
5697
|
const mode = (await getClient().deleteGenericSource(routingKey, opts.hard)).hard ? "permanently deleted" : "soft-deleted";
|
|
5138
|
-
|
|
5698
|
+
const kind = opts.local ? "Local" : "Generic";
|
|
5699
|
+
console.log(`${kind} source ${mode}: ${routingKey}`);
|
|
5139
5700
|
} else {
|
|
5140
5701
|
if (opts.hard) {
|
|
5141
|
-
console.error("Error: --hard flag is only supported with --generic");
|
|
5702
|
+
console.error("Error: --hard flag is only supported with --generic / --local");
|
|
5142
5703
|
process.exit(1);
|
|
5143
5704
|
}
|
|
5144
5705
|
await getClient().delete(`/api/v1/admin/sources/${encodeURIComponent(routingKey)}`);
|
|
@@ -5167,6 +5728,106 @@ function registerSourceCommands(program, getClient) {
|
|
|
5167
5728
|
process.exit(1);
|
|
5168
5729
|
}
|
|
5169
5730
|
});
|
|
5731
|
+
src.command("trigger-local <id>").description("Trigger a run against a local source (reads HEAD when --ref/--sha omitted)").option("--event <event>", "push | pull_request", "push").option("--ref <ref>", "Git ref (default: repo HEAD branch)").option("--sha <sha>", "Commit SHA (default: repo HEAD)").option("--repo-full-name <name>", "owner/name identifier used in the payload", "local/repo").option("--base-url <url>", "Orchestrator base URL", DEFAULT_ORCH_URL).action(async (id, opts) => {
|
|
5732
|
+
try {
|
|
5733
|
+
const { source } = await getClient().getGenericSource(id);
|
|
5734
|
+
const cfg = parseLocalConfig(source);
|
|
5735
|
+
if (!cfg) {
|
|
5736
|
+
console.error(`Error: no local source with id ${id}`);
|
|
5737
|
+
process.exit(1);
|
|
5738
|
+
}
|
|
5739
|
+
const head = opts.ref && opts.sha ? {
|
|
5740
|
+
ref: opts.ref,
|
|
5741
|
+
sha: opts.sha
|
|
5742
|
+
} : readRepoHead(cfg.repoBasePath);
|
|
5743
|
+
const orgId = source.customer_id;
|
|
5744
|
+
const sourceId = source.name;
|
|
5745
|
+
const req = buildLocalTriggerRequest({
|
|
5746
|
+
orgId,
|
|
5747
|
+
sourceId,
|
|
5748
|
+
repoFullName: opts.repoFullName,
|
|
5749
|
+
event: opts.event === "pull_request" ? "pull_request" : "push",
|
|
5750
|
+
ref: head.ref,
|
|
5751
|
+
sha: head.sha,
|
|
5752
|
+
defaultBranch: head.ref.replace("refs/heads/", "")
|
|
5753
|
+
});
|
|
5754
|
+
const status = await sendLocalTrigger(opts.baseUrl, req);
|
|
5755
|
+
console.log(`Triggered local source ${id}: HTTP ${status}`);
|
|
5756
|
+
if (status >= 400) process.exit(1);
|
|
5757
|
+
} catch (err) {
|
|
5758
|
+
console.error(`Error: ${toErrorMessage(err)}`);
|
|
5759
|
+
process.exit(1);
|
|
5760
|
+
}
|
|
5761
|
+
});
|
|
5762
|
+
src.command("install-hook <id>").description("Install a post-receive hook in the local source repo that triggers runs on push").option("--repo <path>", "Repo path (default: the source repoBasePath)").option("--base-url <url>", "Orchestrator base URL", DEFAULT_ORCH_URL).action(async (id, opts) => {
|
|
5763
|
+
try {
|
|
5764
|
+
const { source } = await getClient().getGenericSource(id);
|
|
5765
|
+
const cfg = parseLocalConfig(source);
|
|
5766
|
+
if (!cfg) {
|
|
5767
|
+
console.error(`Error: no local source with id ${id}`);
|
|
5768
|
+
process.exit(1);
|
|
5769
|
+
}
|
|
5770
|
+
const repoPath = opts.repo ?? cfg.repoBasePath;
|
|
5771
|
+
const hookPath = installPostReceiveHook(repoPath, renderPostReceiveHook({
|
|
5772
|
+
sourceId: id,
|
|
5773
|
+
baseUrl: opts.baseUrl
|
|
5774
|
+
}));
|
|
5775
|
+
console.log(`Installed post-receive hook: ${hookPath}`);
|
|
5776
|
+
console.log(`Every push to ${repoPath} will now trigger a run for source ${id}.`);
|
|
5777
|
+
} catch (err) {
|
|
5778
|
+
console.error(`Error: ${toErrorMessage(err)}`);
|
|
5779
|
+
process.exit(1);
|
|
5780
|
+
}
|
|
5781
|
+
});
|
|
5782
|
+
}
|
|
5783
|
+
/** Read the auto-provisioned remote-source row for an org, if it exists. */
|
|
5784
|
+
async function getRemoteSource(db, orgId) {
|
|
5785
|
+
return db.selectFrom("remote_sources").selectAll().where("customer_id", "=", orgId).executeTakeFirst();
|
|
5786
|
+
}
|
|
5787
|
+
//#endregion
|
|
5788
|
+
//#region src/cli/commands/remote-source.ts
|
|
5789
|
+
init_client();
|
|
5790
|
+
function resolveDatabaseUrl$1(explicit) {
|
|
5791
|
+
const url = explicit ?? process.env.KICI_DATABASE_URL;
|
|
5792
|
+
if (!url) throw new Error("Database URL required. Pass --database-url or set KICI_DATABASE_URL.");
|
|
5793
|
+
return url;
|
|
5794
|
+
}
|
|
5795
|
+
function registerRemoteSourceCommands(program) {
|
|
5796
|
+
program.command("remote-source").description("Inspect the auto-provisioned remote-source org anchor").command("show <orgId>").description("Print the remote_sources anchor row for an org (routing key remote:<orgId>).").option("--database-url <url>", "Orchestrator DB URL (else KICI_DATABASE_URL)").option("--format <format>", "Output format: json|table", "table").action(async (orgId, opts) => {
|
|
5797
|
+
let url;
|
|
5798
|
+
try {
|
|
5799
|
+
url = resolveDatabaseUrl$1(opts.databaseUrl);
|
|
5800
|
+
} catch (err) {
|
|
5801
|
+
console.error(`Error: ${toErrorMessage(err)}`);
|
|
5802
|
+
process.exit(1);
|
|
5803
|
+
return;
|
|
5804
|
+
}
|
|
5805
|
+
const pool = createPool$1(url);
|
|
5806
|
+
const db = createDb$1(pool);
|
|
5807
|
+
try {
|
|
5808
|
+
const row = await getRemoteSource(db, orgId);
|
|
5809
|
+
if (!row) {
|
|
5810
|
+
console.error(`No remote_sources anchor found for org ${orgId}.`);
|
|
5811
|
+
process.exit(1);
|
|
5812
|
+
return;
|
|
5813
|
+
}
|
|
5814
|
+
if (opts.format === "json") {
|
|
5815
|
+
console.log(JSON.stringify(row, null, 2));
|
|
5816
|
+
return;
|
|
5817
|
+
}
|
|
5818
|
+
console.log(`customer_id: ${row.customer_id}`);
|
|
5819
|
+
console.log(`routing_key: ${row.routing_key}`);
|
|
5820
|
+
console.log(`cluster_id: ${row.cluster_id ?? "(none)"}`);
|
|
5821
|
+
console.log(`created_at: ${new Date(row.created_at).toISOString()}`);
|
|
5822
|
+
console.log(`updated_at: ${new Date(row.updated_at).toISOString()}`);
|
|
5823
|
+
} catch (err) {
|
|
5824
|
+
console.error(`Error: ${toErrorMessage(err)}`);
|
|
5825
|
+
process.exit(1);
|
|
5826
|
+
} finally {
|
|
5827
|
+
await db.destroy();
|
|
5828
|
+
await pool.end().catch(() => void 0);
|
|
5829
|
+
}
|
|
5830
|
+
});
|
|
5170
5831
|
}
|
|
5171
5832
|
//#endregion
|
|
5172
5833
|
//#region src/cli/commands/workflow.ts
|
|
@@ -6168,7 +6829,7 @@ function registerDebugBundleCommand(program, getClient) {
|
|
|
6168
6829
|
safeRequest(() => client.getText("/metrics"))
|
|
6169
6830
|
]);
|
|
6170
6831
|
const output = fs$1.createWriteStream(outputPath);
|
|
6171
|
-
const archive =
|
|
6832
|
+
const archive = new ZipArchive({ zlib: { level: 6 } });
|
|
6172
6833
|
const finalized = new Promise((resolve, reject) => {
|
|
6173
6834
|
output.on("close", resolve);
|
|
6174
6835
|
archive.on("error", reject);
|
|
@@ -6495,8 +7156,18 @@ function isRoot() {
|
|
|
6495
7156
|
*
|
|
6496
7157
|
* Used by the instance index (`<kiciRoot>/instances.json`) which lives
|
|
6497
7158
|
* outside any per-instance subdir, and as the base for {@link getConfigDir}.
|
|
7159
|
+
*
|
|
7160
|
+
* `KICI_CONFIG_ROOT` overrides the platform default for both privilege scopes.
|
|
7161
|
+
* It exists so a test harness (or a sandboxed run) can point the instance index
|
|
7162
|
+
* at an isolated directory instead of sharing the host's `~/.config/kici/` —
|
|
7163
|
+
* which otherwise lets concurrent installs clobber each other's index.
|
|
6498
7164
|
*/
|
|
6499
7165
|
function kiciConfigRoot(isUserLevel) {
|
|
7166
|
+
const override = process.env.KICI_CONFIG_ROOT;
|
|
7167
|
+
if (override) {
|
|
7168
|
+
const sep = os.platform() === "win32" ? "\\" : "/";
|
|
7169
|
+
return override.endsWith(sep) ? override : override + sep;
|
|
7170
|
+
}
|
|
6500
7171
|
const plat = os.platform();
|
|
6501
7172
|
if (plat === "win32") {
|
|
6502
7173
|
if (isUserLevel) return (process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local")) + "\\kici\\";
|
|
@@ -6644,6 +7315,37 @@ function readKiciVersion() {
|
|
|
6644
7315
|
return "unknown";
|
|
6645
7316
|
}
|
|
6646
7317
|
}
|
|
7318
|
+
/**
|
|
7319
|
+
* Resolve the `@kici-dev/<component>` package version that a {@link LaunchSpec}
|
|
7320
|
+
* will actually execute, by locating the component's entry script among the
|
|
7321
|
+
* spec's tokens and reading the version from the package.json that owns it.
|
|
7322
|
+
*
|
|
7323
|
+
* Returns null when no resolvable entry script is present (e.g. an opaque
|
|
7324
|
+
* `--binary` install) or the owning package.json is missing / name-mismatched.
|
|
7325
|
+
* This is what lets the npm-source upgrade refuse to report a version it can't
|
|
7326
|
+
* stand behind.
|
|
7327
|
+
*/
|
|
7328
|
+
function resolveVersionFromLaunchSpec(spec, component) {
|
|
7329
|
+
const pkgName = `@kici-dev/${component}`;
|
|
7330
|
+
const entryRe = new RegExp(`[/\\\\]@kici-dev[/\\\\]${component}[/\\\\]dist[/\\\\](?:server|standalone)\\.js$`);
|
|
7331
|
+
const entry = [spec.execPath, ...spec.args].find((a) => entryRe.test(a));
|
|
7332
|
+
if (!entry) return null;
|
|
7333
|
+
let dir = path.dirname(entry);
|
|
7334
|
+
for (let i = 0; i < 6; i++) {
|
|
7335
|
+
const candidate = path.join(dir, "package.json");
|
|
7336
|
+
if (fs.existsSync(candidate)) try {
|
|
7337
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, "utf-8"));
|
|
7338
|
+
if (pkg.name === pkgName && typeof pkg.version === "string") return pkg.version;
|
|
7339
|
+
return null;
|
|
7340
|
+
} catch {
|
|
7341
|
+
return null;
|
|
7342
|
+
}
|
|
7343
|
+
const parent = path.dirname(dir);
|
|
7344
|
+
if (parent === dir) break;
|
|
7345
|
+
dir = parent;
|
|
7346
|
+
}
|
|
7347
|
+
return null;
|
|
7348
|
+
}
|
|
6647
7349
|
//#endregion
|
|
6648
7350
|
//#region src/cli/service/instance/index-file.ts
|
|
6649
7351
|
/**
|
|
@@ -6721,27 +7423,54 @@ function removeIndexEntry(kiciRoot, key) {
|
|
|
6721
7423
|
*/
|
|
6722
7424
|
/**
|
|
6723
7425
|
* Reconcile <kiciRoot>/instances.json with the driver's native scan, then
|
|
6724
|
-
* rewrite the index
|
|
6725
|
-
*
|
|
7426
|
+
* rewrite the index so it mirrors the scanned instances that have a known
|
|
7427
|
+
* instanceDir. Two self-heal directions happen here:
|
|
7428
|
+
*
|
|
7429
|
+
* - backward: drop index entries whose unit no longer exists.
|
|
7430
|
+
* - forward: adopt an instanceDir the driver recovered from the unit marker
|
|
7431
|
+
* when the index entry is missing or carries no dir — so a lost or emptied
|
|
7432
|
+
* index rebuilds itself from the init system on the next read.
|
|
7433
|
+
*
|
|
7434
|
+
* The init system is therefore the source of truth for the name→folder mapping
|
|
7435
|
+
* (via the X-KiCI-InstanceDir / KiCIInstanceDir / dev.kici.instance-dir / [KiCI-DIR]
|
|
7436
|
+
* markers), and the index is a pure cache. Returns the merged list filtered to
|
|
7437
|
+
* the requested component + isUserLevel.
|
|
6726
7438
|
*/
|
|
6727
7439
|
async function listInstances(args) {
|
|
6728
7440
|
const { component, isUserLevel, kiciRoot, manager } = args;
|
|
6729
7441
|
const scanForComponent = (await manager.list(isUserLevel)).filter((s) => s.component === component);
|
|
6730
|
-
const scanNames = new Set(scanForComponent.map((s) => s.name));
|
|
6731
7442
|
const index = readIndex(kiciRoot);
|
|
6732
7443
|
const relevantIndex = index.filter((e) => e.component === component && e.isUserLevel === isUserLevel);
|
|
6733
|
-
const
|
|
6734
|
-
|
|
6735
|
-
const indexByName = new Map(survivors.map((e) => [e.name, e]));
|
|
6736
|
-
return scanForComponent.map((s) => {
|
|
7444
|
+
const indexByName = new Map(relevantIndex.map((e) => [e.name, e]));
|
|
7445
|
+
const listed = scanForComponent.map((s) => {
|
|
6737
7446
|
const idx = indexByName.get(s.name);
|
|
6738
7447
|
return {
|
|
6739
7448
|
...s,
|
|
6740
7449
|
component,
|
|
6741
|
-
instanceDir: idx?.instanceDir,
|
|
7450
|
+
instanceDir: idx?.instanceDir ?? s.instanceDir,
|
|
6742
7451
|
source: idx ? "index+scan" : "scan"
|
|
6743
7452
|
};
|
|
6744
7453
|
});
|
|
7454
|
+
const reconciled = [];
|
|
7455
|
+
for (const l of listed) {
|
|
7456
|
+
if (!l.instanceDir) continue;
|
|
7457
|
+
reconciled.push({
|
|
7458
|
+
component,
|
|
7459
|
+
name: l.name,
|
|
7460
|
+
platform: l.platform,
|
|
7461
|
+
isUserLevel,
|
|
7462
|
+
instanceDir: l.instanceDir
|
|
7463
|
+
});
|
|
7464
|
+
}
|
|
7465
|
+
if (!sameIndexSet(relevantIndex, reconciled)) writeIndex(kiciRoot, [...index.filter((e) => !(e.component === component && e.isUserLevel === isUserLevel)), ...reconciled]);
|
|
7466
|
+
return listed;
|
|
7467
|
+
}
|
|
7468
|
+
/** Order-insensitive equality of two index-entry sets (by their full identity). */
|
|
7469
|
+
function sameIndexSet(a, b) {
|
|
7470
|
+
if (a.length !== b.length) return false;
|
|
7471
|
+
const key = (e) => `${e.component}\0${e.name}\0${e.platform}\0${e.isUserLevel}\0${e.instanceDir}`;
|
|
7472
|
+
const setA = new Set(a.map(key));
|
|
7473
|
+
return b.every((e) => setA.has(key(e)));
|
|
6745
7474
|
}
|
|
6746
7475
|
/**
|
|
6747
7476
|
* Resolve the target instance for the current lifecycle invocation.
|
|
@@ -6863,6 +7592,7 @@ var init_systemd = __esmMin((() => {
|
|
|
6863
7592
|
lines.push("[Unit]");
|
|
6864
7593
|
lines.push(`Description=${config.description}`);
|
|
6865
7594
|
if (config.component) lines.push(`X-KiCI-Component=${config.component}`);
|
|
7595
|
+
if (config.instanceDir) lines.push(`X-KiCI-InstanceDir=${config.instanceDir}`);
|
|
6866
7596
|
lines.push("After=network.target postgresql.service");
|
|
6867
7597
|
lines.push("");
|
|
6868
7598
|
lines.push("[Service]");
|
|
@@ -6999,6 +7729,22 @@ var init_systemd = __esmMin((() => {
|
|
|
6999
7729
|
async isInstalled(config) {
|
|
7000
7730
|
return fs.existsSync(this.unitFilePath(config));
|
|
7001
7731
|
}
|
|
7732
|
+
async readLaunchSpec(config) {
|
|
7733
|
+
let content;
|
|
7734
|
+
try {
|
|
7735
|
+
content = fs.readFileSync(this.unitFilePath(config), "utf-8");
|
|
7736
|
+
} catch {
|
|
7737
|
+
return null;
|
|
7738
|
+
}
|
|
7739
|
+
const m = content.match(/^ExecStart=(.+?)\s*$/m);
|
|
7740
|
+
if (!m) return null;
|
|
7741
|
+
const tokens = m[1].trim().split(/\s+/).filter(Boolean);
|
|
7742
|
+
if (tokens.length === 0) return null;
|
|
7743
|
+
return {
|
|
7744
|
+
execPath: tokens[0],
|
|
7745
|
+
args: tokens.slice(1)
|
|
7746
|
+
};
|
|
7747
|
+
}
|
|
7002
7748
|
async list(isUserLevel) {
|
|
7003
7749
|
const unitDir = isUserLevel ? path.join(os.homedir(), ".config", "systemd", "user") : "/etc/systemd/system";
|
|
7004
7750
|
if (!fs.existsSync(unitDir)) return [];
|
|
@@ -7006,13 +7752,16 @@ var init_systemd = __esmMin((() => {
|
|
|
7006
7752
|
for (const entry of fs.readdirSync(unitDir)) {
|
|
7007
7753
|
if (typeof entry !== "string") continue;
|
|
7008
7754
|
if (!entry.startsWith("kici-") || !entry.endsWith(".service")) continue;
|
|
7009
|
-
const
|
|
7755
|
+
const content = fs.readFileSync(path.join(unitDir, entry), "utf-8");
|
|
7756
|
+
const m = content.match(/^X-KiCI-Component=(orchestrator|agent)\s*$/m);
|
|
7010
7757
|
if (!m) continue;
|
|
7758
|
+
const dirMatch = content.match(/^X-KiCI-InstanceDir=(.+?)\s*$/m);
|
|
7011
7759
|
out.push({
|
|
7012
7760
|
name: entry.replace(/\.service$/, ""),
|
|
7013
7761
|
platform: "systemd",
|
|
7014
7762
|
isUserLevel,
|
|
7015
|
-
component: m[1]
|
|
7763
|
+
component: m[1],
|
|
7764
|
+
instanceDir: dirMatch?.[1]
|
|
7016
7765
|
});
|
|
7017
7766
|
}
|
|
7018
7767
|
return out;
|
|
@@ -7071,6 +7820,10 @@ var init_launchd = __esmMin((() => {
|
|
|
7071
7820
|
escapeXml(s) {
|
|
7072
7821
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
7073
7822
|
}
|
|
7823
|
+
/** Inverse of {@link escapeXml} — decode the entities back to literals. */
|
|
7824
|
+
unescapeXml(s) {
|
|
7825
|
+
return s.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/&/g, "&");
|
|
7826
|
+
}
|
|
7074
7827
|
/**
|
|
7075
7828
|
* Generate a launchd plist XML string from a service config.
|
|
7076
7829
|
* Visible for testing.
|
|
@@ -7090,6 +7843,10 @@ var init_launchd = __esmMin((() => {
|
|
|
7090
7843
|
lines.push(" <key>KiCIComponent</key>");
|
|
7091
7844
|
lines.push(` <string>${config.component}</string>`);
|
|
7092
7845
|
}
|
|
7846
|
+
if (config.instanceDir) {
|
|
7847
|
+
lines.push(" <key>KiCIInstanceDir</key>");
|
|
7848
|
+
lines.push(` <string>${this.escapeXml(config.instanceDir)}</string>`);
|
|
7849
|
+
}
|
|
7093
7850
|
lines.push(" <key>ProgramArguments</key>");
|
|
7094
7851
|
lines.push(" <array>");
|
|
7095
7852
|
lines.push(` <string>${this.escapeXml(config.executablePath)}</string>`);
|
|
@@ -7309,15 +8066,33 @@ var init_launchd = __esmMin((() => {
|
|
|
7309
8066
|
}
|
|
7310
8067
|
const match = content.match(/<key>KiCIComponent<\/key>\s*<string>(orchestrator|agent)<\/string>/);
|
|
7311
8068
|
if (!match) continue;
|
|
8069
|
+
const dirMatch = content.match(/<key>KiCIInstanceDir<\/key>\s*<string>([^<]+)<\/string>/);
|
|
7312
8070
|
out.push({
|
|
7313
8071
|
name: entry.replace(/\.plist$/, ""),
|
|
7314
8072
|
platform: "launchd",
|
|
7315
8073
|
isUserLevel,
|
|
7316
|
-
component: match[1]
|
|
8074
|
+
component: match[1],
|
|
8075
|
+
instanceDir: dirMatch ? this.unescapeXml(dirMatch[1]) : void 0
|
|
7317
8076
|
});
|
|
7318
8077
|
}
|
|
7319
8078
|
return out;
|
|
7320
8079
|
}
|
|
8080
|
+
async readLaunchSpec(config) {
|
|
8081
|
+
let content;
|
|
8082
|
+
try {
|
|
8083
|
+
content = fs.readFileSync(this.plistPath(config), "utf-8");
|
|
8084
|
+
} catch {
|
|
8085
|
+
return null;
|
|
8086
|
+
}
|
|
8087
|
+
const arr = content.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/);
|
|
8088
|
+
if (!arr) return null;
|
|
8089
|
+
const strings = [...arr[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map((s) => this.unescapeXml(s[1]));
|
|
8090
|
+
if (strings.length === 0) return null;
|
|
8091
|
+
return {
|
|
8092
|
+
execPath: strings[0],
|
|
8093
|
+
args: strings.slice(1)
|
|
8094
|
+
};
|
|
8095
|
+
}
|
|
7321
8096
|
};
|
|
7322
8097
|
}));
|
|
7323
8098
|
//#endregion
|
|
@@ -7452,61 +8227,61 @@ var init_registry = __esmMin((() => {
|
|
|
7452
8227
|
rolldown: {
|
|
7453
8228
|
"linux-x64": {
|
|
7454
8229
|
name: "rolldown",
|
|
7455
|
-
version: "1.
|
|
8230
|
+
version: "1.1.1",
|
|
7456
8231
|
platform: "linux",
|
|
7457
8232
|
arch: "x64",
|
|
7458
|
-
url: "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.
|
|
7459
|
-
sha256: "
|
|
8233
|
+
url: "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.1.tgz",
|
|
8234
|
+
sha256: "55bf455e4d6c5df19636c40b434718891a574214bbf09a50e2e7b21b9f640d56",
|
|
7460
8235
|
extractPath: "package/",
|
|
7461
8236
|
archiveType: "tar.gz"
|
|
7462
8237
|
},
|
|
7463
8238
|
"linux-arm64": {
|
|
7464
8239
|
name: "rolldown",
|
|
7465
|
-
version: "1.
|
|
8240
|
+
version: "1.1.1",
|
|
7466
8241
|
platform: "linux",
|
|
7467
8242
|
arch: "arm64",
|
|
7468
|
-
url: "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.
|
|
7469
|
-
sha256: "
|
|
8243
|
+
url: "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.1.tgz",
|
|
8244
|
+
sha256: "af458cfd2ce237a5161bdfdcef960e710c394b966537e1d1374840fb7fc78308",
|
|
7470
8245
|
extractPath: "package/",
|
|
7471
8246
|
archiveType: "tar.gz"
|
|
7472
8247
|
},
|
|
7473
8248
|
"darwin-x64": {
|
|
7474
8249
|
name: "rolldown",
|
|
7475
|
-
version: "1.
|
|
8250
|
+
version: "1.1.1",
|
|
7476
8251
|
platform: "darwin",
|
|
7477
8252
|
arch: "x64",
|
|
7478
|
-
url: "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.
|
|
7479
|
-
sha256: "
|
|
8253
|
+
url: "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.1.tgz",
|
|
8254
|
+
sha256: "31bd12457aa41646dcee674d7fa73ec2c4625ef5d34d0ef6c6823cad811de3e2",
|
|
7480
8255
|
extractPath: "package/",
|
|
7481
8256
|
archiveType: "tar.gz"
|
|
7482
8257
|
},
|
|
7483
8258
|
"darwin-arm64": {
|
|
7484
8259
|
name: "rolldown",
|
|
7485
|
-
version: "1.
|
|
8260
|
+
version: "1.1.1",
|
|
7486
8261
|
platform: "darwin",
|
|
7487
8262
|
arch: "arm64",
|
|
7488
|
-
url: "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.
|
|
7489
|
-
sha256: "
|
|
8263
|
+
url: "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.1.tgz",
|
|
8264
|
+
sha256: "8399e7a87640ffe485ba16289f6c3a7ab69b25d09b43e5896441372bcf6a02f9",
|
|
7490
8265
|
extractPath: "package/",
|
|
7491
8266
|
archiveType: "tar.gz"
|
|
7492
8267
|
},
|
|
7493
8268
|
"win32-x64": {
|
|
7494
8269
|
name: "rolldown",
|
|
7495
|
-
version: "1.
|
|
8270
|
+
version: "1.1.1",
|
|
7496
8271
|
platform: "win32",
|
|
7497
8272
|
arch: "x64",
|
|
7498
|
-
url: "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.
|
|
7499
|
-
sha256: "
|
|
8273
|
+
url: "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.1.tgz",
|
|
8274
|
+
sha256: "2b2c34d530db0e2cf15cdbe328822d5d385b19ee3b5aba55771587027b9afa17",
|
|
7500
8275
|
extractPath: "package/",
|
|
7501
8276
|
archiveType: "tar.gz"
|
|
7502
8277
|
},
|
|
7503
8278
|
"win32-arm64": {
|
|
7504
8279
|
name: "rolldown",
|
|
7505
|
-
version: "1.
|
|
8280
|
+
version: "1.1.1",
|
|
7506
8281
|
platform: "win32",
|
|
7507
8282
|
arch: "arm64",
|
|
7508
|
-
url: "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.
|
|
7509
|
-
sha256: "
|
|
8283
|
+
url: "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.1.tgz",
|
|
8284
|
+
sha256: "d7431cb75d0bed42dd8794b89c3d73ac7129a36465ac020519726f2a9dca1d7c",
|
|
7510
8285
|
extractPath: "package/",
|
|
7511
8286
|
archiveType: "tar.gz"
|
|
7512
8287
|
}
|
|
@@ -7615,7 +8390,8 @@ var init_windows = __esmMin((() => {
|
|
|
7615
8390
|
cmdParts.push("--", `"${config.executablePath}"`);
|
|
7616
8391
|
for (const arg of config.args ?? []) cmdParts.push(`"${arg}"`);
|
|
7617
8392
|
execSync(cmdParts.join(" "), { stdio: "pipe" });
|
|
7618
|
-
const
|
|
8393
|
+
const dirSuffix = config.component && config.instanceDir ? ` [KiCI-DIR:${config.instanceDir}]` : "";
|
|
8394
|
+
const descText = config.component ? `[KiCI:${config.component}] ${config.description}${dirSuffix}` : config.description;
|
|
7619
8395
|
execSync(`sc.exe description ${config.name} "${descText.replace(/"/g, "\\\"")}"`, { stdio: "pipe" });
|
|
7620
8396
|
execSync(`sc.exe config ${config.name} start= auto`, { stdio: "pipe" });
|
|
7621
8397
|
const actions = config.restartPolicy.delays.map((d) => `restart/${d * 1e3}`).join("/");
|
|
@@ -7726,18 +8502,39 @@ var init_windows = __esmMin((() => {
|
|
|
7726
8502
|
for (const row of rows) {
|
|
7727
8503
|
if (!row || typeof row !== "object") continue;
|
|
7728
8504
|
const r = row;
|
|
7729
|
-
const
|
|
8505
|
+
const desc = typeof r.Description === "string" ? r.Description : "";
|
|
8506
|
+
const match = desc.match(/^\[KiCI:(orchestrator|agent)\]/);
|
|
7730
8507
|
if (!match) continue;
|
|
7731
8508
|
if (typeof r.Name !== "string") continue;
|
|
8509
|
+
const dirMatch = desc.match(/\[KiCI-DIR:([^\]]+)\]/);
|
|
7732
8510
|
out.push({
|
|
7733
8511
|
name: r.Name,
|
|
7734
8512
|
platform: "windows",
|
|
7735
8513
|
isUserLevel,
|
|
7736
|
-
component: match[1]
|
|
8514
|
+
component: match[1],
|
|
8515
|
+
instanceDir: dirMatch?.[1]
|
|
7737
8516
|
});
|
|
7738
8517
|
}
|
|
7739
8518
|
return out;
|
|
7740
8519
|
}
|
|
8520
|
+
async readLaunchSpec(config) {
|
|
8521
|
+
let raw;
|
|
8522
|
+
try {
|
|
8523
|
+
raw = execSync(`sc.exe qc ${config.name}`, { stdio: "pipe" }).toString();
|
|
8524
|
+
} catch {
|
|
8525
|
+
return null;
|
|
8526
|
+
}
|
|
8527
|
+
const m = raw.match(/BINARY_PATH_NAME\s*:\s*(.+)/);
|
|
8528
|
+
if (!m) return null;
|
|
8529
|
+
const sep = m[1].indexOf(" -- ");
|
|
8530
|
+
if (sep === -1) return null;
|
|
8531
|
+
const tokens = [...m[1].slice(sep + 4).matchAll(/"([^"]*)"|(\S+)/g)].map((t) => t[1] ?? t[2]);
|
|
8532
|
+
if (tokens.length === 0 || !tokens[0]) return null;
|
|
8533
|
+
return {
|
|
8534
|
+
execPath: tokens[0],
|
|
8535
|
+
args: tokens.slice(1)
|
|
8536
|
+
};
|
|
8537
|
+
}
|
|
7741
8538
|
};
|
|
7742
8539
|
}));
|
|
7743
8540
|
//#endregion
|
|
@@ -7878,6 +8675,7 @@ function generateComposeYaml(config) {
|
|
|
7878
8675
|
if (config.component) {
|
|
7879
8676
|
lines.push(" labels:");
|
|
7880
8677
|
lines.push(` dev.kici.component: ${config.component}`);
|
|
8678
|
+
if (config.instanceDir) lines.push(` dev.kici.instance-dir: "${config.instanceDir}"`);
|
|
7881
8679
|
}
|
|
7882
8680
|
return lines.join("\n") + "\n";
|
|
7883
8681
|
}
|
|
@@ -7970,19 +8768,25 @@ var init_compose = __esmMin((() => {
|
|
|
7970
8768
|
}
|
|
7971
8769
|
if (!row || typeof row !== "object") continue;
|
|
7972
8770
|
const r = row;
|
|
7973
|
-
const
|
|
8771
|
+
const labels = typeof r.Labels === "string" ? r.Labels : "";
|
|
8772
|
+
const m = labels.match(/dev\.kici\.component=(orchestrator|agent)/);
|
|
7974
8773
|
if (!m) continue;
|
|
7975
8774
|
const name = typeof r.Names === "string" ? r.Names : String(r.Names ?? "");
|
|
7976
8775
|
if (!name) continue;
|
|
8776
|
+
const dirMatch = labels.match(/dev\.kici\.instance-dir=([^,]+)/);
|
|
7977
8777
|
out.push({
|
|
7978
8778
|
name,
|
|
7979
8779
|
platform: "compose",
|
|
7980
8780
|
isUserLevel,
|
|
7981
|
-
component: m[1]
|
|
8781
|
+
component: m[1],
|
|
8782
|
+
instanceDir: dirMatch?.[1]
|
|
7982
8783
|
});
|
|
7983
8784
|
}
|
|
7984
8785
|
return out;
|
|
7985
8786
|
}
|
|
8787
|
+
async readLaunchSpec(_config) {
|
|
8788
|
+
return null;
|
|
8789
|
+
}
|
|
7986
8790
|
};
|
|
7987
8791
|
}));
|
|
7988
8792
|
//#endregion
|
|
@@ -8104,7 +8908,8 @@ async function resolveUpgradeTarget(args) {
|
|
|
8104
8908
|
maxRetries: 5,
|
|
8105
8909
|
windowSeconds: 300
|
|
8106
8910
|
},
|
|
8107
|
-
component
|
|
8911
|
+
component,
|
|
8912
|
+
instanceDir: resolved.instanceDir
|
|
8108
8913
|
},
|
|
8109
8914
|
installBase: resolved.manifest.installBase,
|
|
8110
8915
|
resolvedInstance: resolved
|
|
@@ -8243,6 +9048,51 @@ function updateSymlinkAtomic(installBase, component, version) {
|
|
|
8243
9048
|
* 5. Update symlink (Unix) or service registration (Windows)
|
|
8244
9049
|
* 6. Start service
|
|
8245
9050
|
*/
|
|
9051
|
+
/**
|
|
9052
|
+
* Resolve the target version for an npm-source upgrade (no --from/--url).
|
|
9053
|
+
*
|
|
9054
|
+
* The npm-source flow assumes the operator already ran
|
|
9055
|
+
* `npm install -g @kici-dev/<pkg>@<version>`, which overwrites the global
|
|
9056
|
+
* package in place. The running `kici-admin` binary is therefore the new
|
|
9057
|
+
* version, and `running` is its self-reported version. We default the target
|
|
9058
|
+
* to it, or validate an explicitly-passed --version matches — a mismatch means
|
|
9059
|
+
* the npm install did not actually update the global binary.
|
|
9060
|
+
*/
|
|
9061
|
+
function resolveNpmSourceVersion(opts) {
|
|
9062
|
+
if (opts.running === "unknown" || opts.running.trim() === "") throw new Error("npm-source upgrade: could not determine the running package version. Pass --from <archive> / --url <url> + --version for an archive-based upgrade instead.");
|
|
9063
|
+
if (opts.requested && opts.requested !== opts.running) throw new Error(`npm-source upgrade: requested --version ${opts.requested} does not match the installed package version ${opts.running}. Run \`npm install -g @kici-dev/<pkg>@${opts.requested}\` first, then re-run the upgrade.`);
|
|
9064
|
+
return opts.running;
|
|
9065
|
+
}
|
|
9066
|
+
/**
|
|
9067
|
+
* Decide whether an npm-source upgrade may proceed, given the version the
|
|
9068
|
+
* invoking CLI is (`invoked`) and the version the installed unit will actually
|
|
9069
|
+
* launch (`launched`, or null when unresolvable — e.g. an opaque --binary
|
|
9070
|
+
* install). On success `manifestVersion` is the value to persist (null = leave
|
|
9071
|
+
* the manifest's kiciVersion unchanged because we couldn't verify it).
|
|
9072
|
+
*/
|
|
9073
|
+
function verifyNpmSourceLaunch(opts) {
|
|
9074
|
+
const { component, invoked, launched, launchedPath, force } = opts;
|
|
9075
|
+
if (launched === null) {
|
|
9076
|
+
if (force) return {
|
|
9077
|
+
ok: true,
|
|
9078
|
+
version: invoked,
|
|
9079
|
+
manifestVersion: null
|
|
9080
|
+
};
|
|
9081
|
+
return {
|
|
9082
|
+
ok: false,
|
|
9083
|
+
reason: "npm-source upgrade aborted: could not determine the version the installed unit will launch (custom --binary install or unparseable launch target). Pass --force to restart without version verification (the manifest version is left unchanged)."
|
|
9084
|
+
};
|
|
9085
|
+
}
|
|
9086
|
+
if (launched !== invoked) return {
|
|
9087
|
+
ok: false,
|
|
9088
|
+
reason: `npm-source upgrade aborted: this kici-admin is version ${invoked}, but the installed unit will launch version ${launched}${launchedPath ? ` (via ${launchedPath})` : ""}. Your \`npm install -g\` updated a different install than the service is pinned to. Install ${invoked} under the unit's runtime (the node its ExecStart points at), or re-run \`kici-admin ${component} install\` to repoint the unit, then retry.`
|
|
9089
|
+
};
|
|
9090
|
+
return {
|
|
9091
|
+
ok: true,
|
|
9092
|
+
version: launched,
|
|
9093
|
+
manifestVersion: launched
|
|
9094
|
+
};
|
|
9095
|
+
}
|
|
8246
9096
|
async function performVersionedUpgrade(component, opts) {
|
|
8247
9097
|
try {
|
|
8248
9098
|
const platform = detectPlatform(opts.platform);
|
|
@@ -8272,8 +9122,8 @@ async function performVersionedUpgrade(component, opts) {
|
|
|
8272
9122
|
return;
|
|
8273
9123
|
}
|
|
8274
9124
|
if (!opts.from && !opts.url) {
|
|
8275
|
-
|
|
8276
|
-
|
|
9125
|
+
await performNpmSourceUpgrade(component, config, resolvedInstance, manager, opts);
|
|
9126
|
+
return;
|
|
8277
9127
|
}
|
|
8278
9128
|
if (!opts.version) {
|
|
8279
9129
|
console.error("Error: --version is required to specify the target version");
|
|
@@ -8375,6 +9225,54 @@ async function performVersionedUpgrade(component, opts) {
|
|
|
8375
9225
|
}
|
|
8376
9226
|
}
|
|
8377
9227
|
/**
|
|
9228
|
+
* npm-source upgrade (no --from/--url): the operator has already run
|
|
9229
|
+
* `npm install -g @kici-dev/<pkg>@<version>`, replacing the global package
|
|
9230
|
+
* in place. The systemd/launchd/Windows unit's ExecStart points at that global
|
|
9231
|
+
* package (see install.ts → import.meta.resolve), so a stop+start re-executes
|
|
9232
|
+
* the freshly-installed code. No archive, no versioned dir, no symlink.
|
|
9233
|
+
*/
|
|
9234
|
+
async function performNpmSourceUpgrade(component, config, resolvedInstance, manager, opts) {
|
|
9235
|
+
const invoked = readKiciVersion();
|
|
9236
|
+
resolveNpmSourceVersion({
|
|
9237
|
+
requested: opts.version,
|
|
9238
|
+
running: invoked
|
|
9239
|
+
});
|
|
9240
|
+
if (!await manager.isInstalled(config)) {
|
|
9241
|
+
console.error(`Error: service "${config.name}" is not installed`);
|
|
9242
|
+
process.exit(1);
|
|
9243
|
+
}
|
|
9244
|
+
const spec = await manager.readLaunchSpec(config);
|
|
9245
|
+
const verdict = verifyNpmSourceLaunch({
|
|
9246
|
+
component,
|
|
9247
|
+
invoked,
|
|
9248
|
+
launched: spec ? resolveVersionFromLaunchSpec(spec, component) : null,
|
|
9249
|
+
launchedPath: spec?.execPath ?? null,
|
|
9250
|
+
force: opts.force ?? false
|
|
9251
|
+
});
|
|
9252
|
+
if (!verdict.ok) {
|
|
9253
|
+
console.error(`Error: ${verdict.reason}`);
|
|
9254
|
+
process.exit(1);
|
|
9255
|
+
}
|
|
9256
|
+
if (!opts.yes) {
|
|
9257
|
+
console.log(`This will restart "${config.name}" onto the npm-installed version ${verdict.version}.`);
|
|
9258
|
+
console.log(" The service will be stopped briefly during the restart.");
|
|
9259
|
+
console.log("");
|
|
9260
|
+
if (!await confirm$2("Proceed with upgrade?")) {
|
|
9261
|
+
console.log("Upgrade cancelled.");
|
|
9262
|
+
return;
|
|
9263
|
+
}
|
|
9264
|
+
}
|
|
9265
|
+
console.log("Restarting service onto the npm-installed package...");
|
|
9266
|
+
if ((await manager.status(config)).state === "running") await manager.stop(config);
|
|
9267
|
+
await manager.start(config);
|
|
9268
|
+
if (verdict.manifestVersion !== null) writeManifest(resolvedInstance.instanceDir, {
|
|
9269
|
+
...resolvedInstance.manifest,
|
|
9270
|
+
kiciVersion: verdict.manifestVersion
|
|
9271
|
+
});
|
|
9272
|
+
else console.log("Note: manifest version left unchanged (verification was skipped via --force).");
|
|
9273
|
+
console.log(`Upgrade complete: service "${config.name}" is now running version ${verdict.version}.`);
|
|
9274
|
+
}
|
|
9275
|
+
/**
|
|
8378
9276
|
* Handle --rollback: switch symlink to the previous version and restart.
|
|
8379
9277
|
*/
|
|
8380
9278
|
async function handleRollback(component, platform, installBase, config, manager, opts) {
|
|
@@ -8722,7 +9620,7 @@ function registerOrchestratorInstall(orchestrator) {
|
|
|
8722
9620
|
})).find((c) => c.name === serviceName);
|
|
8723
9621
|
if (existing && existing.instanceDir !== instanceDir && !opts.force) {
|
|
8724
9622
|
const at = existing.instanceDir ?? "(no manifest)";
|
|
8725
|
-
console.error(`Error: an orchestrator instance "${serviceName}" is already installed at ${at}.
|
|
9623
|
+
console.error(`Error: an orchestrator instance "${serviceName}" is already installed at ${at}.\n\nUpgrading this service? Don't re-run install — installing again is for first-time\nsetup, not upgrades. For an npm-global install run \`npm install -g kici-admin@latest\`\nthen \`kici-admin orchestrator restart\`; for a versioned-directory install run\n\`kici-admin orchestrator upgrade\`.\n\nInstalling a second, separate instance? Pass a different --name or --instance-dir,\nor --force to take over this one.`);
|
|
8726
9624
|
process.exit(1);
|
|
8727
9625
|
}
|
|
8728
9626
|
const configDir = getConfigDir(serviceName, userLevel);
|
|
@@ -8791,6 +9689,7 @@ function registerOrchestratorInstall(orchestrator) {
|
|
|
8791
9689
|
isUserLevel: userLevel,
|
|
8792
9690
|
user: opts.user,
|
|
8793
9691
|
component: "orchestrator",
|
|
9692
|
+
instanceDir,
|
|
8794
9693
|
restartPolicy: DEFAULT_RESTART_POLICY
|
|
8795
9694
|
};
|
|
8796
9695
|
await manager.install(config);
|
|
@@ -9297,6 +10196,7 @@ function registerAgentInstall(agent) {
|
|
|
9297
10196
|
workingDirectory: configDir,
|
|
9298
10197
|
isUserLevel: userLevel,
|
|
9299
10198
|
component: "agent",
|
|
10199
|
+
instanceDir,
|
|
9300
10200
|
restartPolicy: DEFAULT_RESTART_POLICY
|
|
9301
10201
|
};
|
|
9302
10202
|
await manager.install(config);
|
|
@@ -9730,6 +10630,7 @@ const TOKEN_PREFIX = "kici_join_v1";
|
|
|
9730
10630
|
const DEFAULT_EXPIRY_MS = 36e5;
|
|
9731
10631
|
const TOKEN_ALREADY_USED_MESSAGE = "Join token has already been used. Generate a new token with: kici admin create-join-token";
|
|
9732
10632
|
var JoinTokenManager = class {
|
|
10633
|
+
deps;
|
|
9733
10634
|
constructor(deps) {
|
|
9734
10635
|
this.deps = deps;
|
|
9735
10636
|
}
|
|
@@ -9859,6 +10760,7 @@ const DEFAULT_EXPIRY_DAYS = 90;
|
|
|
9859
10760
|
* Used by the coordinator to manage credentials for all cluster members.
|
|
9860
10761
|
*/
|
|
9861
10762
|
var PeerCredentialStore = class {
|
|
10763
|
+
db;
|
|
9862
10764
|
constructor(db) {
|
|
9863
10765
|
this.db = db;
|
|
9864
10766
|
}
|
|
@@ -10087,6 +10989,211 @@ function registerPeerCommands(program, _getClient) {
|
|
|
10087
10989
|
}
|
|
10088
10990
|
});
|
|
10089
10991
|
}
|
|
10992
|
+
/**
|
|
10993
|
+
* The ONE status-derivation function — used by the store and the kici-admin
|
|
10994
|
+
* host CLI. `ready` requires the host to be genuinely live (connected to some
|
|
10995
|
+
* instance AND heartbeat fresh — the freshness check catches a crashed
|
|
10996
|
+
* instance that never cleared `connected_instance_id`). It never returns
|
|
10997
|
+
* `ready` for a not-currently-live host: a not-live `static` reads
|
|
10998
|
+
* `unreachable` (the declared-but-absent alarm), a not-live `ephemeral` reads
|
|
10999
|
+
* `stale` (scaled down, awaiting reap).
|
|
11000
|
+
*/
|
|
11001
|
+
function deriveHostStatus(row, nowMs, graceMs) {
|
|
11002
|
+
const ageMs = nowMs - new Date(row.last_seen).getTime();
|
|
11003
|
+
if (row.connected_instance_id !== null && ageMs <= graceMs) return "ready";
|
|
11004
|
+
if (row.lifecycle_class === "ephemeral") return "stale";
|
|
11005
|
+
return "unreachable";
|
|
11006
|
+
}
|
|
11007
|
+
/**
|
|
11008
|
+
* Durable, cluster-shared roster of every agent the cluster has ever enrolled.
|
|
11009
|
+
*
|
|
11010
|
+
* The in-memory `AgentRegistry` reconciles into this table on register
|
|
11011
|
+
* (`upsert`) / unregister (`markDisconnected`), with a coarse heartbeat
|
|
11012
|
+
* (`stampLastSeen`). All liveness writes are owner-guarded by `instanceId` so a
|
|
11013
|
+
* stale disconnect from an old instance can never clobber a row a different
|
|
11014
|
+
* instance now owns. The leader-only reaper deletes `ephemeral` rows past their
|
|
11015
|
+
* ttl via `reapEphemeralPastTtl`; `static` rows persist and read `unreachable`.
|
|
11016
|
+
*/
|
|
11017
|
+
var HostRosterStore = class {
|
|
11018
|
+
db;
|
|
11019
|
+
constructor(db) {
|
|
11020
|
+
this.db = db;
|
|
11021
|
+
}
|
|
11022
|
+
/** Idempotent upsert on agent_id; stamps connected_instance_id + last_seen. */
|
|
11023
|
+
async upsert(input) {
|
|
11024
|
+
const labelsJson = JSON.stringify(input.labels);
|
|
11025
|
+
await this.db.insertInto("host_roster").values({
|
|
11026
|
+
agent_id: input.agentId,
|
|
11027
|
+
token_id: input.tokenId,
|
|
11028
|
+
lifecycle_class: input.lifecycleClass,
|
|
11029
|
+
labels: labelsJson,
|
|
11030
|
+
hostname: input.hostname,
|
|
11031
|
+
platform: input.platform,
|
|
11032
|
+
arch: input.arch,
|
|
11033
|
+
connected_instance_id: input.instanceId,
|
|
11034
|
+
last_seen: sql`now()`,
|
|
11035
|
+
updated_at: sql`now()`
|
|
11036
|
+
}).onConflict((oc) => oc.column("agent_id").doUpdateSet({
|
|
11037
|
+
token_id: input.tokenId,
|
|
11038
|
+
lifecycle_class: input.lifecycleClass,
|
|
11039
|
+
labels: labelsJson,
|
|
11040
|
+
hostname: input.hostname,
|
|
11041
|
+
platform: input.platform,
|
|
11042
|
+
arch: input.arch,
|
|
11043
|
+
connected_instance_id: input.instanceId,
|
|
11044
|
+
last_seen: sql`now()`,
|
|
11045
|
+
updated_at: sql`now()`
|
|
11046
|
+
})).execute();
|
|
11047
|
+
}
|
|
11048
|
+
/** Clear liveness on disconnect — but only if THIS instance still owns it. */
|
|
11049
|
+
async markDisconnected(agentId, instanceId) {
|
|
11050
|
+
await this.db.updateTable("host_roster").set({
|
|
11051
|
+
connected_instance_id: null,
|
|
11052
|
+
updated_at: sql`now()`
|
|
11053
|
+
}).where("agent_id", "=", agentId).where("connected_instance_id", "=", instanceId).execute();
|
|
11054
|
+
}
|
|
11055
|
+
/** Coarse heartbeat stamp — same owner-guard as markDisconnected. */
|
|
11056
|
+
async stampLastSeen(agentId, instanceId) {
|
|
11057
|
+
await this.db.updateTable("host_roster").set({ last_seen: sql`now()` }).where("agent_id", "=", agentId).where("connected_instance_id", "=", instanceId).execute();
|
|
11058
|
+
}
|
|
11059
|
+
/** Operator pre-declare of a static host before its agent dials in. */
|
|
11060
|
+
async declareStatic(input) {
|
|
11061
|
+
await this.db.insertInto("host_roster").values({
|
|
11062
|
+
agent_id: input.agentId,
|
|
11063
|
+
token_id: null,
|
|
11064
|
+
lifecycle_class: "static",
|
|
11065
|
+
labels: JSON.stringify(input.labels),
|
|
11066
|
+
hostname: input.hostname ?? null,
|
|
11067
|
+
connected_instance_id: null,
|
|
11068
|
+
last_seen: sql`now()`,
|
|
11069
|
+
updated_at: sql`now()`
|
|
11070
|
+
}).onConflict((oc) => oc.column("agent_id").doNothing()).execute();
|
|
11071
|
+
}
|
|
11072
|
+
async get(agentId) {
|
|
11073
|
+
return await this.db.selectFrom("host_roster").selectAll().where("agent_id", "=", agentId).executeTakeFirst() ?? null;
|
|
11074
|
+
}
|
|
11075
|
+
async listAll() {
|
|
11076
|
+
return this.db.selectFrom("host_roster").selectAll().orderBy("agent_id", "asc").execute();
|
|
11077
|
+
}
|
|
11078
|
+
/**
|
|
11079
|
+
* Resolve every roster host matching a `runsOnAll` predicate (OR-of-AND
|
|
11080
|
+
* include groups, minus exclude labels), tagged with its derived status. This
|
|
11081
|
+
* is the host-fanout resolver: it returns declared-but-absent static hosts
|
|
11082
|
+
* (status `unreachable`) so the caller can apply `onUnreachable` — the live
|
|
11083
|
+
* registry alone cannot name an expected-but-absent host.
|
|
11084
|
+
*/
|
|
11085
|
+
async findMatching(include, exclude, graceMs) {
|
|
11086
|
+
const rows = await this.db.selectFrom("host_roster").selectAll().execute();
|
|
11087
|
+
const now = Date.now();
|
|
11088
|
+
const out = [];
|
|
11089
|
+
for (const row of rows) {
|
|
11090
|
+
const labels = JSON.parse(row.labels);
|
|
11091
|
+
const set = new Set(labels);
|
|
11092
|
+
if (exclude.some((e) => matcherSatisfiedBy(e, set))) continue;
|
|
11093
|
+
if (include.length && !include.some((grp) => grp.every((m) => matcherSatisfiedBy(m, set)))) continue;
|
|
11094
|
+
out.push({
|
|
11095
|
+
agentId: row.agent_id,
|
|
11096
|
+
host: row.hostname ?? row.agent_id,
|
|
11097
|
+
labels,
|
|
11098
|
+
lifecycleClass: row.lifecycle_class,
|
|
11099
|
+
connectedInstanceId: row.connected_instance_id,
|
|
11100
|
+
status: deriveHostStatus(row, now, graceMs),
|
|
11101
|
+
platform: row.platform,
|
|
11102
|
+
arch: row.arch
|
|
11103
|
+
});
|
|
11104
|
+
}
|
|
11105
|
+
out.sort((a, b) => a.agentId.localeCompare(b.agentId));
|
|
11106
|
+
return out;
|
|
11107
|
+
}
|
|
11108
|
+
/**
|
|
11109
|
+
* Count `static` (declared) hosts whose derived status is `unreachable` —
|
|
11110
|
+
* the "declared-but-absent" alarm population. Reuses the single-source
|
|
11111
|
+
* {@link deriveHostStatus} so the count never diverges from what
|
|
11112
|
+
* `kici-admin host list` shows. A not-currently-connected static host reads
|
|
11113
|
+
* `unreachable` regardless of grace (only the connected-but-stale case
|
|
11114
|
+
* depends on `graceMs`).
|
|
11115
|
+
*/
|
|
11116
|
+
async countStaticUnreachable(graceMs) {
|
|
11117
|
+
const rows = await this.db.selectFrom("host_roster").selectAll().where("lifecycle_class", "=", "static").execute();
|
|
11118
|
+
const now = Date.now();
|
|
11119
|
+
return rows.filter((r) => deriveHostStatus(r, now, graceMs) === "unreachable").length;
|
|
11120
|
+
}
|
|
11121
|
+
/** Delete ephemeral rows whose last_seen is older than ttl. Returns count. */
|
|
11122
|
+
async reapEphemeralPastTtl(ttlMs) {
|
|
11123
|
+
const res = await this.db.deleteFrom("host_roster").where("lifecycle_class", "=", "ephemeral").where("last_seen", "<", sql`now() - (${ttlMs}::text || ' milliseconds')::interval`).executeTakeFirst();
|
|
11124
|
+
return Number(res.numDeletedRows ?? 0n);
|
|
11125
|
+
}
|
|
11126
|
+
};
|
|
11127
|
+
//#endregion
|
|
11128
|
+
//#region src/cli/commands/host.ts
|
|
11129
|
+
/**
|
|
11130
|
+
* The CLI reads the roster DB directly (no full orchestrator config load), so
|
|
11131
|
+
* it sources the grace window from the same env var the orchestrator config
|
|
11132
|
+
* defaults from. Default mirrors `config.ts` rosterGraceMs (300_000).
|
|
11133
|
+
*/
|
|
11134
|
+
function rosterGraceMs() {
|
|
11135
|
+
const v = Number(process.env.KICI_ROSTER_GRACE_MS);
|
|
11136
|
+
return Number.isFinite(v) && v > 0 ? v : 3e5;
|
|
11137
|
+
}
|
|
11138
|
+
function formatHostTable(rows) {
|
|
11139
|
+
if (rows.length === 0) return "No hosts in the roster.";
|
|
11140
|
+
const header = "Agent ID | Class | Status | Instance | Last Seen | Labels";
|
|
11141
|
+
const sep = "-".repeat(57);
|
|
11142
|
+
const now = Date.now();
|
|
11143
|
+
const grace = rosterGraceMs();
|
|
11144
|
+
return [
|
|
11145
|
+
header,
|
|
11146
|
+
sep,
|
|
11147
|
+
...rows.map((r) => {
|
|
11148
|
+
const status = deriveHostStatus(r, now, grace);
|
|
11149
|
+
const labels = JSON.parse(r.labels).join(",");
|
|
11150
|
+
return `${r.agent_id} | ${r.lifecycle_class} | ${status} | ${r.connected_instance_id ?? "-"} | ${new Date(r.last_seen).toISOString()} | ${labels}`;
|
|
11151
|
+
})
|
|
11152
|
+
].join("\n");
|
|
11153
|
+
}
|
|
11154
|
+
function registerHostCommands(program) {
|
|
11155
|
+
const host = program.command("host").description("Inspect and declare the host roster");
|
|
11156
|
+
host.command("list").description("List all roster hosts").option("--json", "Output JSON").action(async (opts) => {
|
|
11157
|
+
try {
|
|
11158
|
+
const rows = await withDb((db) => new HostRosterStore(db).listAll());
|
|
11159
|
+
if (opts.json) {
|
|
11160
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
11161
|
+
return;
|
|
11162
|
+
}
|
|
11163
|
+
console.log(formatHostTable(rows));
|
|
11164
|
+
} catch (err) {
|
|
11165
|
+
console.error(`Error: ${toErrorMessage(err)}`);
|
|
11166
|
+
process.exit(1);
|
|
11167
|
+
}
|
|
11168
|
+
});
|
|
11169
|
+
host.command("get").description("Show one roster host").requiredOption("--agent-id <id>", "Agent id").option("--json", "Output JSON").action(async (opts) => {
|
|
11170
|
+
try {
|
|
11171
|
+
const row = await withDb((db) => new HostRosterStore(db).get(opts.agentId));
|
|
11172
|
+
if (!row) {
|
|
11173
|
+
console.error(`No host found: ${opts.agentId}`);
|
|
11174
|
+
process.exit(1);
|
|
11175
|
+
}
|
|
11176
|
+
console.log(opts.json ? JSON.stringify(row, null, 2) : formatHostTable([row]));
|
|
11177
|
+
} catch (err) {
|
|
11178
|
+
console.error(`Error: ${toErrorMessage(err)}`);
|
|
11179
|
+
process.exit(1);
|
|
11180
|
+
}
|
|
11181
|
+
});
|
|
11182
|
+
host.command("declare").description("Pre-declare a static host before it connects").requiredOption("--agent-id <id>", "Agent id the host will register as").option("--labels <labels>", "Comma-separated labels", "").option("--hostname <name>", "Hostname").action(async (opts) => {
|
|
11183
|
+
try {
|
|
11184
|
+
const labels = opts.labels ? String(opts.labels).split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
11185
|
+
await withDb((db) => new HostRosterStore(db).declareStatic({
|
|
11186
|
+
agentId: opts.agentId,
|
|
11187
|
+
labels,
|
|
11188
|
+
hostname: opts.hostname
|
|
11189
|
+
}));
|
|
11190
|
+
console.log(`Declared static host: ${opts.agentId}`);
|
|
11191
|
+
} catch (err) {
|
|
11192
|
+
console.error(`Error: ${toErrorMessage(err)}`);
|
|
11193
|
+
process.exit(1);
|
|
11194
|
+
}
|
|
11195
|
+
});
|
|
11196
|
+
}
|
|
10090
11197
|
//#endregion
|
|
10091
11198
|
//#region src/cli/commands/org-settings.ts
|
|
10092
11199
|
function formatSettings(s, format) {
|
|
@@ -11448,6 +12555,8 @@ var init_execution_steps = __esmMin((() => {
|
|
|
11448
12555
|
enabled: true
|
|
11449
12556
|
};
|
|
11450
12557
|
ExecutionStepsAdapter = class {
|
|
12558
|
+
kdb;
|
|
12559
|
+
instanceId;
|
|
11451
12560
|
db = "orchestrator";
|
|
11452
12561
|
table = "execution_steps";
|
|
11453
12562
|
tenantColumn = "routing_key";
|
|
@@ -11603,6 +12712,8 @@ var init_execution_jobs = __esmMin((() => {
|
|
|
11603
12712
|
enabled: true
|
|
11604
12713
|
};
|
|
11605
12714
|
ExecutionJobsAdapter = class {
|
|
12715
|
+
kdb;
|
|
12716
|
+
instanceId;
|
|
11606
12717
|
db = "orchestrator";
|
|
11607
12718
|
table = "execution_jobs";
|
|
11608
12719
|
tenantColumn = "routing_key";
|
|
@@ -11771,6 +12882,8 @@ var init_execution_runs = __esmMin((() => {
|
|
|
11771
12882
|
enabled: true
|
|
11772
12883
|
};
|
|
11773
12884
|
ExecutionRunsAdapter = class {
|
|
12885
|
+
kdb;
|
|
12886
|
+
instanceId;
|
|
11774
12887
|
db = "orchestrator";
|
|
11775
12888
|
table = "execution_runs";
|
|
11776
12889
|
tenantColumn = "routing_key";
|
|
@@ -12002,6 +13115,8 @@ var init_secret_audit_log = __esmMin((() => {
|
|
|
12002
13115
|
enabled: true
|
|
12003
13116
|
};
|
|
12004
13117
|
SecretAuditLogAdapter = class {
|
|
13118
|
+
kdb;
|
|
13119
|
+
instanceId;
|
|
12005
13120
|
db = "orchestrator";
|
|
12006
13121
|
table = "secret_audit_log";
|
|
12007
13122
|
tenantColumn = "routing_key";
|
|
@@ -12255,6 +13370,8 @@ var init_access_log = __esmMin((() => {
|
|
|
12255
13370
|
enabled: true
|
|
12256
13371
|
};
|
|
12257
13372
|
AccessLogAdapter = class {
|
|
13373
|
+
kdb;
|
|
13374
|
+
instanceId;
|
|
12258
13375
|
db = "orchestrator";
|
|
12259
13376
|
table = "access_log";
|
|
12260
13377
|
tenantColumn = "org_id";
|
|
@@ -12510,6 +13627,8 @@ var init_event_log = __esmMin((() => {
|
|
|
12510
13627
|
enabled: true
|
|
12511
13628
|
};
|
|
12512
13629
|
EventLogAdapter = class {
|
|
13630
|
+
kdb;
|
|
13631
|
+
instanceId;
|
|
12513
13632
|
db = "orchestrator";
|
|
12514
13633
|
table = "event_log";
|
|
12515
13634
|
tenantColumn = "routing_key";
|
|
@@ -14590,6 +15709,7 @@ var FirecrackerApiError = class extends Error {
|
|
|
14590
15709
|
* the `socketPath` option for HTTP-over-Unix-socket communication.
|
|
14591
15710
|
*/
|
|
14592
15711
|
var FirecrackerApi = class {
|
|
15712
|
+
socketPath;
|
|
14593
15713
|
constructor(socketPath) {
|
|
14594
15714
|
this.socketPath = socketPath;
|
|
14595
15715
|
}
|
|
@@ -16577,6 +17697,7 @@ async function writeConfigFile(path, config) {
|
|
|
16577
17697
|
await writeFile(path, stringify(config), "utf-8");
|
|
16578
17698
|
}
|
|
16579
17699
|
var JoinClient = class {
|
|
17700
|
+
options;
|
|
16580
17701
|
constructor(options) {
|
|
16581
17702
|
this.options = options;
|
|
16582
17703
|
if (!options.platformUrl && !options.peerUrl) throw new Error("Either --platform or --peer must be specified");
|
|
@@ -16748,6 +17869,7 @@ function buildProgram() {
|
|
|
16748
17869
|
registerConfigCommands(program, getClient);
|
|
16749
17870
|
registerDbCommands(program, getClient);
|
|
16750
17871
|
registerSourceCommands(program, getClient);
|
|
17872
|
+
registerRemoteSourceCommands(program);
|
|
16751
17873
|
registerWorkflowCommands(program, getClient);
|
|
16752
17874
|
registerRunsCommands(program, getClient);
|
|
16753
17875
|
registerEventLogCommands(program, getClient);
|
|
@@ -16761,6 +17883,7 @@ function buildProgram() {
|
|
|
16761
17883
|
registerScalerCommands(program);
|
|
16762
17884
|
registerAgentServiceCommands(program);
|
|
16763
17885
|
registerPeerCommands(program, getClient);
|
|
17886
|
+
registerHostCommands(program);
|
|
16764
17887
|
registerOrgSettingsCommands(program, getClient);
|
|
16765
17888
|
registerClusterNameCommands(program, getClient);
|
|
16766
17889
|
registerMaintenanceCommands(program, getClient);
|
|
@@ -16779,8 +17902,27 @@ function buildProgram() {
|
|
|
16779
17902
|
function runCli(argv = process.argv) {
|
|
16780
17903
|
buildProgram().parse(argv);
|
|
16781
17904
|
}
|
|
16782
|
-
|
|
17905
|
+
/**
|
|
17906
|
+
* Canonicalize a path through any symlinks, falling back to a plain `resolve`
|
|
17907
|
+
* when the target does not exist on disk. The entry-point guard compares the
|
|
17908
|
+
* invoked script path against this module's own path; both sides must be fully
|
|
17909
|
+
* canonicalized or a symlinked invocation path silently fails the match. The
|
|
17910
|
+
* canonical example is the light-package launcher running
|
|
17911
|
+
* `node /tmp/.../kici-admin.cjs` on macOS, where `/tmp` is a symlink to
|
|
17912
|
+
* `/private/tmp`: `process.argv[1]` keeps the `/tmp` form while the bundle's
|
|
17913
|
+
* `import.meta.url` resolves to the real `/private/tmp` path, so an
|
|
17914
|
+
* un-canonicalized comparison is always false and the CLI exits 0 without
|
|
17915
|
+
* running any command.
|
|
17916
|
+
*/
|
|
17917
|
+
function canonicalize(p) {
|
|
17918
|
+
try {
|
|
17919
|
+
return realpathSync(resolve(p));
|
|
17920
|
+
} catch {
|
|
17921
|
+
return resolve(p);
|
|
17922
|
+
}
|
|
17923
|
+
}
|
|
17924
|
+
if (canonicalize(process.argv[1] ?? "") === canonicalize(fileURLToPath(import.meta.url))) runCli();
|
|
16783
17925
|
//#endregion
|
|
16784
|
-
export { buildProgram, runCli };
|
|
17926
|
+
export { buildProgram, canonicalize, runCli };
|
|
16785
17927
|
|
|
16786
17928
|
//# sourceMappingURL=cli.js.map
|