@kici-dev/orchestrator 0.20.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/api-client.d.ts +2 -0
- package/dist/cli/commands/runs.d.ts +3 -1
- package/dist/cli/commands/shared/run-ref.d.ts +4 -0
- package/dist/cli/commands/source.d.ts +1 -1
- package/dist/cli.js +113 -27
- package/dist/db/types.d.ts +3 -3
- package/dist/index.js +1 -0
- package/dist/pipeline/dispatch-matched-workflow.d.ts +6 -4
- package/dist/pipeline/test-pipeline.d.ts +21 -1
- package/dist/reporting/check-run-reporter.d.ts +2 -0
- package/dist/reporting/check-run-summary.d.ts +12 -0
- package/dist/reporting/execution-tracker.d.ts +14 -3
- package/dist/reporting/run-workflow-names.d.ts +2 -2
- package/dist/reporting/step-display-order.d.ts +9 -5
- package/dist/server.js +258 -91
- package/dist/standalone.js +238 -89
- package/dist/ws/test-relay-handlers.d.ts +8 -0
- package/installer-image-digests.json +3 -3
- package/package.json +4 -4
- package/sbom.spdx.json +53 -53
package/dist/cli/api-client.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { type AgentStepLogs } from '@kici-dev/engine';
|
|
|
7
7
|
* recorded, or a log is stored and holds nothing. `null` when the step has
|
|
8
8
|
* lines, or when the orchestrator does not report which case applies.
|
|
9
9
|
*/
|
|
10
|
-
export declare function emptyStepLogNote(
|
|
10
|
+
export declare function emptyStepLogNote(
|
|
11
|
+
/** The step index, or an init step's `init[<n>]` label. */
|
|
12
|
+
step: number | string, logs: Pick<AgentStepLogs, 'totalLines' | 'recorded'>): string | null;
|
|
11
13
|
export declare function registerRunsCommands(program: Command, getClient: () => AdminApiClient): void;
|
|
12
14
|
//# sourceMappingURL=runs.d.ts.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `kici-admin source` commands: webhook source CRUD. A secret flag takes a
|
|
3
3
|
* literal value, `@/path/file`, `--from-env <VAR_NAME>` or `--stdin`.
|
|
4
4
|
*/
|
|
5
|
-
import type
|
|
5
|
+
import { type Command } from 'commander';
|
|
6
6
|
import type { AdminApiClient } from '../api-client.js';
|
|
7
7
|
/** Satisfied by both the HTTP `GenericSourceResponse` and the DB `GenericWebhookSource`. */
|
|
8
8
|
interface ListableGenericRow {
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs, { chmodSync, closeSync, constants, createReadStream, createWriteStrea
|
|
|
8
8
|
import { Command, Option } from "commander";
|
|
9
9
|
import { AGENT_DEFAULT_PORT, LOGGER_ENV_VARS, ORCHESTRATOR_DEFAULT_PORT, defineEnv, validateUnknownKiciVars } from "@kici-dev/shared/env";
|
|
10
10
|
import { AgentPlatform, BaseColdStore, ChunkLru, PgTableAdapterBase, ReadinessStatus, addLogsToArchive, clearDispatchQueueDirect, computeMigrationsHash, createColdStoreAdminOps, createContextTemplateDirect, createDb, createDbRole, createLogger, createPool, createReadOnlyDbUser, createS3Client, decrypt, deleteContextDirect, deriveKey, diagnoseExitCode, diffFingerprints, dropAndCreateDatabase, emitKiciEventDirect, encrypt, ensureDatabase, fingerprintSchema, formatBytes, formatDiffEntry, formatUptime, isSchemaCurrent, kiciMkdtemp, listCheckRunTrackingDirect, listContextsDirect, listExecutionRunsDirect, listQueueDirect, listRegistrationsDirect, maskDatabaseUrl, parseDatabaseUrl, prunePeerCredentialsDirect, purgeContextsDirect, purgeScopedSecretsDirect, purgeSecretBackendsDirect, purgeStaleExecutionDirect, purgeStaleSourcesDirect, registerWorkflowManualDirect, resetRaftStateDirect, seedContextBindingDirect, seedContextDirect, serializeError, setContextPolicyDirect, setContextSecretDirect, sha256, showContextDirect, showExecutionRunDirect, showQueueEntryDirect, showRegistrationDirect, splitAgentPlatform, storeMigrationContentHash, toCommandError, toErrorMessage } from "@kici-dev/shared";
|
|
11
|
-
import { AccessLogSource, ApprovalDecision, CiTrustLevel, ContextType, ExecutionJobStatus, FleetHostWriteRefusal, ForkPolicy, HoldType, JOIN_PROTOCOL_UNSUPPORTED_MESSAGE, JOIN_PROTOCOL_VERSION, JoinErrorCode, KICI_AGENT_ENV_PREFIX, KNOWN_ROLES, MinimumTrustSchema, OrchRole, OrchestratorMode, PLATFORM_CONNECTED_MODES, PRIVILEGED_ROOT_LABEL, PROTOCOL_VERSION, PeerForgetOutcome, RuntimeFact, ScalerBackendType, ScalerEventType, ScalerReloadOutcome, ScalerVmStatus, ScalerVmStopOutcome, ScalerVmTracker, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, WS_MAX_PAYLOAD_BYTES, accessLogWarmSqlCase, agentLabelOf, assertValidSecretKey, attestationVerifyStatusSchema, canonicalizeLabel, canonicalizeLabels, canonicalizeMatcher, flattenActor, formatExpiryDuration, getAccessLogColdDays, getSecretAuditLogColdDays, joinResponseSchema, matcherSatisfiedBy, minAccessLogWarmDays, minSecretAuditLogWarmDays, parseExpiryDuration, parseHostPropertyAssignments, platformToTaints, resolveHeldRunId, resolveRoleLabels, resourceRequestNestedSchema, runtimeLabel, scalerAgentLabels, scalerPlatformSchema, secretAuditLogWarmSqlCase, shouldRecordAccess, sleep, toIsoString } from "@kici-dev/engine";
|
|
11
|
+
import { AccessLogSource, ApprovalDecision, CiTrustLevel, ContextType, ExecutionJobStatus, FleetHostWriteRefusal, ForkPolicy, HoldType, JOIN_PROTOCOL_UNSUPPORTED_MESSAGE, JOIN_PROTOCOL_VERSION, JoinErrorCode, KICI_AGENT_ENV_PREFIX, KNOWN_ROLES, MinimumTrustSchema, OrchRole, OrchestratorMode, PLATFORM_CONNECTED_MODES, PRIVILEGED_ROOT_LABEL, PROTOCOL_VERSION, PeerForgetOutcome, RuntimeFact, ScalerBackendType, ScalerEventType, ScalerReloadOutcome, ScalerVmStatus, ScalerVmStopOutcome, ScalerVmTracker, StepPhase, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, WS_MAX_PAYLOAD_BYTES, accessLogWarmSqlCase, agentLabelOf, assertValidSecretKey, attestationVerifyStatusSchema, canonicalizeLabel, canonicalizeLabels, canonicalizeMatcher, compareStepsByPhase, flattenActor, formatExpiryDuration, getAccessLogColdDays, getSecretAuditLogColdDays, initStepIndex, initStepLabel, initStepType, invalidRunIdMessage, joinResponseSchema, matcherSatisfiedBy, minAccessLogWarmDays, minSecretAuditLogWarmDays, parseExpiryDuration, parseHostPropertyAssignments, parseInitStepRef, parseRunIdRef, pickRunIdMatch, platformToTaints, resolveHeldRunId, resolveRoleLabels, resourceRequestNestedSchema, runIdHasPrefix, runtimeLabel, scalerAgentLabels, scalerPlatformSchema, secretAuditLogWarmSqlCase, shouldRecordAccess, sleep, stepPhase, toIsoString } from "@kici-dev/engine";
|
|
12
12
|
import crypto$1, { createDecipheriv, createHash, createHmac, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
13
13
|
import { createInterface } from "node:readline";
|
|
14
14
|
import { access, chmod, constants as constants$1, copyFile, link, mkdir, open, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
@@ -521,6 +521,7 @@ var AdminApiClient = class {
|
|
|
521
521
|
workflowName: opts?.workflowName,
|
|
522
522
|
repo: opts?.repo,
|
|
523
523
|
since: opts?.since,
|
|
524
|
+
runIdPrefix: opts?.runIdPrefix,
|
|
524
525
|
limit: opts?.limit || void 0,
|
|
525
526
|
offset: opts?.offset || void 0
|
|
526
527
|
});
|
|
@@ -18801,8 +18802,10 @@ function renderSourceListText(githubSources, genericSources, hasOrg, opts) {
|
|
|
18801
18802
|
console.log("Tip: use --org <orgId> to also list generic sources.");
|
|
18802
18803
|
}
|
|
18803
18804
|
}
|
|
18804
|
-
/**
|
|
18805
|
-
|
|
18805
|
+
/** Mirrors the kici-admin global `--url`: KICI_ADMIN_URL, read at parse time, else the default. */
|
|
18806
|
+
function baseUrlOption() {
|
|
18807
|
+
return new Option("--base-url <url>", "Orchestrator base URL").env("KICI_ADMIN_URL").default(DEFAULT_ADMIN_URL);
|
|
18808
|
+
}
|
|
18806
18809
|
/** Null when the row is not a valid local source. */
|
|
18807
18810
|
function parseLocalConfig(s) {
|
|
18808
18811
|
if (s.provider_type !== "local" || !s.git_config) return null;
|
|
@@ -19414,7 +19417,7 @@ function registerSourceCommands(program, getClient) {
|
|
|
19414
19417
|
await getClient().disableGenericSource(id);
|
|
19415
19418
|
console.log(`Generic source disabled: ${id}`);
|
|
19416
19419
|
}));
|
|
19417
|
-
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("--before <sha>", "Commit before the push; the changed files are before..sha (default: parent of --sha)").option("--repo-full-name <name>", "owner/name identifier used in the payload", "local/repo").
|
|
19420
|
+
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("--before <sha>", "Commit before the push; the changed files are before..sha (default: parent of --sha)").option("--repo-full-name <name>", "owner/name identifier used in the payload", "local/repo").addOption(baseUrlOption()).action(cliAction(async (id, opts) => {
|
|
19418
19421
|
const { source } = await getClient().getGenericSource(id);
|
|
19419
19422
|
const cfg = parseLocalConfig(source);
|
|
19420
19423
|
if (!cfg) {
|
|
@@ -19441,7 +19444,7 @@ function registerSourceCommands(program, getClient) {
|
|
|
19441
19444
|
console.log(`Triggered local source ${id}: HTTP ${status}`);
|
|
19442
19445
|
if (status >= 400) process.exit(1);
|
|
19443
19446
|
}));
|
|
19444
|
-
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)").
|
|
19447
|
+
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)").addOption(baseUrlOption()).action(cliAction(async (id, opts) => {
|
|
19445
19448
|
const { source } = await getClient().getGenericSource(id);
|
|
19446
19449
|
const cfg = parseLocalConfig(source);
|
|
19447
19450
|
if (!cfg) {
|
|
@@ -19617,6 +19620,33 @@ function registerWorkflowCommands(program, getClient) {
|
|
|
19617
19620
|
}));
|
|
19618
19621
|
}
|
|
19619
19622
|
//#endregion
|
|
19623
|
+
//#region src/cli/commands/shared/run-ref.ts
|
|
19624
|
+
/** Resolve a `kici-admin runs` run id: a full id, or a unique prefix (like a git short SHA). */
|
|
19625
|
+
const AMBIGUOUS_SHOWN = 5;
|
|
19626
|
+
const FIND_HINT = "'kici-admin runs list' shows full run ids";
|
|
19627
|
+
/** The full run id `ref` names; a prefix resolves through the run list. */
|
|
19628
|
+
async function resolveRunRef(client, ref) {
|
|
19629
|
+
const parsed = parseRunIdRef(ref);
|
|
19630
|
+
if (parsed.kind === "uuid") return parsed.runId;
|
|
19631
|
+
if (parsed.kind === "invalid") throw new Error(invalidRunIdMessage(ref, parsed.reason, FIND_HINT));
|
|
19632
|
+
const { runs, total } = await client.listRuns({
|
|
19633
|
+
runIdPrefix: parsed.hex,
|
|
19634
|
+
limit: AMBIGUOUS_SHOWN
|
|
19635
|
+
});
|
|
19636
|
+
const ids = runs.map((r) => String(r.runId));
|
|
19637
|
+
const matches = ids.filter((id) => runIdHasPrefix(id, parsed.hex));
|
|
19638
|
+
if (matches.length < ids.length && total > ids.length) throw new Error(prefixUnsupported(ref));
|
|
19639
|
+
return pickRunIdMatch({
|
|
19640
|
+
ref,
|
|
19641
|
+
matches,
|
|
19642
|
+
unseen: total - ids.length,
|
|
19643
|
+
findHint: FIND_HINT
|
|
19644
|
+
});
|
|
19645
|
+
}
|
|
19646
|
+
function prefixUnsupported(ref) {
|
|
19647
|
+
return `This orchestrator cannot look up the run id prefix '${ref}'; pass the full run id (${FIND_HINT}).`;
|
|
19648
|
+
}
|
|
19649
|
+
//#endregion
|
|
19620
19650
|
//#region src/cli/commands/runs.ts
|
|
19621
19651
|
function formatDuration(ms) {
|
|
19622
19652
|
if (ms == null) return "-";
|
|
@@ -19657,6 +19687,42 @@ function workflowProvenanceLines(run) {
|
|
|
19657
19687
|
if (run.workflowBranch) lines.push(` Workflow branch: ${run.workflowBranch}`);
|
|
19658
19688
|
return lines;
|
|
19659
19689
|
}
|
|
19690
|
+
/**
|
|
19691
|
+
* A job's steps in run order: setup and init pseudo-steps first, whatever order the
|
|
19692
|
+
* orchestrator sent. An older orchestrator orders init steps by their stored index, last.
|
|
19693
|
+
*/
|
|
19694
|
+
function stepsInRunOrder(steps) {
|
|
19695
|
+
return [...steps].sort(compareStepsByPhase);
|
|
19696
|
+
}
|
|
19697
|
+
/**
|
|
19698
|
+
* The `#` cell of a step row: the step index, or `init[<n>]` for an init step, whose
|
|
19699
|
+
* stored index is a high internal value and not a position among the steps.
|
|
19700
|
+
*/
|
|
19701
|
+
function stepNumberCell(step) {
|
|
19702
|
+
const initIndex = initStepIndex(step.stepType);
|
|
19703
|
+
return initIndex === void 0 ? String(step.stepIndex) : initStepLabel(initIndex);
|
|
19704
|
+
}
|
|
19705
|
+
/** The `type` cell of a `runs show` step row: blank for a plain step, `init` for any init. */
|
|
19706
|
+
function stepTypeCell(stepType) {
|
|
19707
|
+
if (stepPhase(stepType) === StepPhase.enum.init) return StepPhase.enum.init;
|
|
19708
|
+
return stepType === "step" ? "" : stepType;
|
|
19709
|
+
}
|
|
19710
|
+
/**
|
|
19711
|
+
* The stored step index `runs logs --step` names. An init step is shown as `init[<n>]`,
|
|
19712
|
+
* not by its stored index, so that label resolves through the job's steps; any other
|
|
19713
|
+
* value passes through for the server to validate.
|
|
19714
|
+
*/
|
|
19715
|
+
async function resolveStepRef(client, runId, jobId, step) {
|
|
19716
|
+
const ref = String(step);
|
|
19717
|
+
const initIndex = parseInitStepRef(ref);
|
|
19718
|
+
if (initIndex === void 0) return ref;
|
|
19719
|
+
const { jobs } = await client.getRunJobs(runId, { includeSteps: true });
|
|
19720
|
+
const job = jobs.find((j) => j.jobId === jobId);
|
|
19721
|
+
if (!job) throw new Error(`Job ${jobId} not found in run ${runId}`);
|
|
19722
|
+
const match = (job.steps ?? []).find((s) => s.stepType === initStepType(initIndex));
|
|
19723
|
+
if (!match) throw new Error(`Job ${jobId} has no ${initStepLabel(initIndex)} step`);
|
|
19724
|
+
return String(match.stepIndex);
|
|
19725
|
+
}
|
|
19660
19726
|
/** The step index of a job's setup log: the clone, the install and the module load. */
|
|
19661
19727
|
const SETUP_LOG_STEP_INDEX = -1;
|
|
19662
19728
|
/**
|
|
@@ -19664,11 +19730,12 @@ const SETUP_LOG_STEP_INDEX = -1;
|
|
|
19664
19730
|
* recorded, or a log is stored and holds nothing. `null` when the step has
|
|
19665
19731
|
* lines, or when the orchestrator does not report which case applies.
|
|
19666
19732
|
*/
|
|
19667
|
-
function emptyStepLogNote(
|
|
19733
|
+
function emptyStepLogNote(step, logs) {
|
|
19668
19734
|
if (logs.totalLines > 0 || logs.recorded === void 0) return null;
|
|
19669
|
-
if (
|
|
19670
|
-
return logs.recorded ? `(the log for step ${
|
|
19735
|
+
if (step === SETUP_LOG_STEP_INDEX) return logs.recorded ? "(the setup log for this job is empty)" : "(no setup log recorded for this job)";
|
|
19736
|
+
return logs.recorded ? `(the log for step ${step} is empty)` : `(no log recorded for step ${step})`;
|
|
19671
19737
|
}
|
|
19738
|
+
const RUN_ID_ARG_HELP = "Run id, or its first 4 or more characters";
|
|
19672
19739
|
function registerRunsCommands(program, getClient) {
|
|
19673
19740
|
const runs = program.command("runs").description("Inspect execution runs, jobs, and steps");
|
|
19674
19741
|
runs.command("list").description("List execution runs (admin API: /api/v1/admin/runs)").option("--status <statuses>", "Filter by run status. Accepts a single value or a comma-separated list (e.g. success,failed)").option("--workflow-name <name>", "Filter by workflow name. A run that spans several workflows matches any of them").option("--repo <ownerRepo>", "Filter by repo identifier (owner/repo)").option("--since <iso8601>", "Only include runs with created_at strictly later than this ISO-8601 timestamp").option("--count", "Return only the count of matching runs, skipping the row listing").option("--limit <n>", "Max results (default 20, max 100)", "20").option("--offset <n>", "Skip first N results", "0").option("--json", "Emit raw JSON instead of a table").action(cliAction(async (opts) => {
|
|
@@ -19723,7 +19790,8 @@ function registerRunsCommands(program, getClient) {
|
|
|
19723
19790
|
console.log("");
|
|
19724
19791
|
console.log(`Showing ${data.runs.length} of ${data.total} (offset ${data.offset}, limit ${data.limit})`);
|
|
19725
19792
|
}));
|
|
19726
|
-
runs.command("show
|
|
19793
|
+
runs.command("show").argument("<runId>", RUN_ID_ARG_HELP).description("Show run detail with jobs and steps").option("--json", "Emit raw JSON instead of formatted output").action(cliAction(async (runRef, opts) => {
|
|
19794
|
+
const runId = await resolveRunRef(getClient(), runRef);
|
|
19727
19795
|
const client = getClient();
|
|
19728
19796
|
const [runRespRaw, jobsRespRaw] = await Promise.all([client.getRun(runId), client.getRunJobs(runId, { includeSteps: true })]);
|
|
19729
19797
|
const runResp = runRespRaw;
|
|
@@ -19800,18 +19868,19 @@ function registerRunsCommands(program, getClient) {
|
|
|
19800
19868
|
"duration",
|
|
19801
19869
|
"type"
|
|
19802
19870
|
];
|
|
19803
|
-
const stepRows = (job.steps ?? []).map((s) => [
|
|
19804
|
-
|
|
19871
|
+
const stepRows = stepsInRunOrder(job.steps ?? []).map((s) => [
|
|
19872
|
+
stepNumberCell(s),
|
|
19805
19873
|
s.stepName,
|
|
19806
19874
|
s.status,
|
|
19807
19875
|
s.exitCode != null ? String(s.exitCode) : "-",
|
|
19808
19876
|
formatDuration(s.durationMs),
|
|
19809
|
-
s.stepType
|
|
19877
|
+
stepTypeCell(s.stepType)
|
|
19810
19878
|
]);
|
|
19811
19879
|
console.log(renderTable(stepHeaders, stepRows));
|
|
19812
19880
|
}
|
|
19813
19881
|
}));
|
|
19814
|
-
runs.command("structured
|
|
19882
|
+
runs.command("structured").argument("<runId>", RUN_ID_ARG_HELP).description("Show the provenance-tagged structured run result (agent read path; /structured)").option("--json", "Emit the raw AgentRunResult (untrusted envelopes preserved)").action(cliAction(async (runRef, opts) => {
|
|
19883
|
+
const runId = await resolveRunRef(getClient(), runRef);
|
|
19815
19884
|
const result = await getClient().getRunStructured(runId);
|
|
19816
19885
|
if (opts.json) {
|
|
19817
19886
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -19854,8 +19923,8 @@ function registerRunsCommands(program, getClient) {
|
|
|
19854
19923
|
"exit",
|
|
19855
19924
|
"duration"
|
|
19856
19925
|
];
|
|
19857
|
-
const stepRows = job.steps.map((s) => [
|
|
19858
|
-
|
|
19926
|
+
const stepRows = stepsInRunOrder(job.steps).map((s) => [
|
|
19927
|
+
stepNumberCell(s),
|
|
19859
19928
|
uv(s.stepName),
|
|
19860
19929
|
s.status,
|
|
19861
19930
|
s.exitCode != null ? String(s.exitCode) : "-",
|
|
@@ -19864,7 +19933,8 @@ function registerRunsCommands(program, getClient) {
|
|
|
19864
19933
|
console.log(renderTable(stepHeaders, stepRows));
|
|
19865
19934
|
}
|
|
19866
19935
|
}));
|
|
19867
|
-
runs.command("jobs
|
|
19936
|
+
runs.command("jobs").argument("<runId>", RUN_ID_ARG_HELP).description("List jobs for a run (admin API: /api/v1/admin/runs/:runId/jobs)").option("--include-steps", "Embed step list inside each job (default false)").option("--json", "Emit raw JSON instead of a table").action(cliAction(async (runRef, opts) => {
|
|
19937
|
+
const runId = await resolveRunRef(getClient(), runRef);
|
|
19868
19938
|
const response = await getClient().getRunJobs(runId, { includeSteps: Boolean(opts.includeSteps) });
|
|
19869
19939
|
if (opts.json) {
|
|
19870
19940
|
console.log(JSON.stringify(response, null, 2));
|
|
@@ -19907,8 +19977,8 @@ function registerRunsCommands(program, getClient) {
|
|
|
19907
19977
|
"exit",
|
|
19908
19978
|
"duration"
|
|
19909
19979
|
];
|
|
19910
|
-
const stepRows = steps.map((s) => [
|
|
19911
|
-
|
|
19980
|
+
const stepRows = stepsInRunOrder(steps).map((s) => [
|
|
19981
|
+
stepNumberCell(s),
|
|
19912
19982
|
s.stepName,
|
|
19913
19983
|
s.status,
|
|
19914
19984
|
s.exitCode != null ? String(s.exitCode) : "-",
|
|
@@ -19918,10 +19988,12 @@ function registerRunsCommands(program, getClient) {
|
|
|
19918
19988
|
}
|
|
19919
19989
|
}
|
|
19920
19990
|
}));
|
|
19921
|
-
runs.command("logs
|
|
19991
|
+
runs.command("logs").argument("<runId>", RUN_ID_ARG_HELP).description("Print a page of a step log (admin API: /api/v1/admin/runs/:runId/jobs/:jobId/steps/:i/logs)").requiredOption("--job <jobId>", "Job id (the dispatch_queue row id for an eval round)").option("--step <n>", "Step index (default 0; -1 is the job setup log), or an init step as init[<n>]", "0").option("--limit <n>", "Max lines to return (default 500, server caps at 2000)", "500").option("--cursor <c>", "Line-offset cursor from a previous page").option("--json", "Emit raw JSON instead of plain lines").action(cliAction(async (runRef, opts) => {
|
|
19992
|
+
const runId = await resolveRunRef(getClient(), runRef);
|
|
19993
|
+
const stepIndex = await resolveStepRef(getClient(), runId, String(opts.job), opts.step);
|
|
19922
19994
|
const params = new URLSearchParams({ limit: String(opts.limit) });
|
|
19923
19995
|
if (opts.cursor) params.set("cursor", String(opts.cursor));
|
|
19924
|
-
const path = `/api/v1/admin/runs/${encodeURIComponent(runId)}/jobs/${encodeURIComponent(String(opts.job))}/steps/${encodeURIComponent(
|
|
19996
|
+
const path = `/api/v1/admin/runs/${encodeURIComponent(runId)}/jobs/${encodeURIComponent(String(opts.job))}/steps/${encodeURIComponent(stepIndex)}/logs?${params.toString()}`;
|
|
19925
19997
|
const response = await getClient().get(path);
|
|
19926
19998
|
if (opts.json) {
|
|
19927
19999
|
console.log(JSON.stringify(response, null, 2));
|
|
@@ -19929,10 +20001,12 @@ function registerRunsCommands(program, getClient) {
|
|
|
19929
20001
|
}
|
|
19930
20002
|
for (const line of response.lines) console.log(line.value);
|
|
19931
20003
|
if (response.nextCursor) console.error(`(more: re-run with --cursor ${response.nextCursor})`);
|
|
19932
|
-
const
|
|
20004
|
+
const initIndex = parseInitStepRef(String(opts.step));
|
|
20005
|
+
const note = emptyStepLogNote(initIndex === void 0 ? parseInt(stepIndex, 10) : initStepLabel(initIndex), response);
|
|
19933
20006
|
if (note) console.error(note);
|
|
19934
20007
|
}));
|
|
19935
|
-
runs.command("ephemeral-key
|
|
20008
|
+
runs.command("ephemeral-key").argument("<runId>", RUN_ID_ARG_HELP).description("Show whether the run-ephemeral key has been scrubbed yet").option("--json", "Emit raw JSON instead of plain text").action(cliAction(async (runRef, opts) => {
|
|
20009
|
+
const runId = await resolveRunRef(getClient(), runRef);
|
|
19936
20010
|
const response = await getClient().getRunEphemeralKey(runId);
|
|
19937
20011
|
if (opts.json) {
|
|
19938
20012
|
console.log(JSON.stringify(response, null, 2));
|
|
@@ -19946,7 +20020,8 @@ function registerRunsCommands(program, getClient) {
|
|
|
19946
20020
|
console.log(`created_at: -`);
|
|
19947
20021
|
}
|
|
19948
20022
|
}));
|
|
19949
|
-
runs.command("secret-outputs
|
|
20023
|
+
runs.command("secret-outputs").argument("<runId>", RUN_ID_ARG_HELP).description("List per-job secret outputs (masked by default; --reveal decrypts and audits)").option("--output-key <key>", "Filter to a single output_key").option("--reveal", "Decrypt and print plaintext values. Audited with actor=secret-outputs.reveal; requires secret.reveal permission").option("--json", "Emit raw JSON instead of a table").action(cliAction(async (runRef, opts) => {
|
|
20024
|
+
const runId = await resolveRunRef(getClient(), runRef);
|
|
19950
20025
|
if (opts.reveal) process.stderr.write("⚠ --reveal will decrypt secret values; this call is recorded in secret_audit_log.\n");
|
|
19951
20026
|
const response = await getClient().getRunSecretOutputs(runId, {
|
|
19952
20027
|
outputKey: opts.outputKey,
|
|
@@ -26881,7 +26956,7 @@ init_manifest();
|
|
|
26881
26956
|
const ALL_PLATFORMS = AgentPlatform.options;
|
|
26882
26957
|
/** The orchestrator's own version (single-version invariant) = the packaged agent version. */
|
|
26883
26958
|
function resolveKiciVersion() {
|
|
26884
|
-
return "0.
|
|
26959
|
+
return "0.21.0";
|
|
26885
26960
|
}
|
|
26886
26961
|
/** Parse the --platform value: default set | single | CSV | `all`. */
|
|
26887
26962
|
function parsePlatforms(raw) {
|
|
@@ -30591,6 +30666,16 @@ function registerQueueCommands(program, getClient) {
|
|
|
30591
30666
|
}
|
|
30592
30667
|
//#endregion
|
|
30593
30668
|
//#region src/cli/commands/execution.ts
|
|
30669
|
+
/**
|
|
30670
|
+
* The full run id `execution show` was given. Over the admin API a prefix resolves
|
|
30671
|
+
* like `kici-admin runs show`; the direct-database path takes a full id only.
|
|
30672
|
+
*/
|
|
30673
|
+
async function resolveExecutionRunRef(ref, client) {
|
|
30674
|
+
if (client) return resolveRunRef(client, ref);
|
|
30675
|
+
const parsed = parseRunIdRef(ref);
|
|
30676
|
+
if (parsed.kind === "uuid") return parsed.runId;
|
|
30677
|
+
throw new Error(`Invalid run id '${ref}': --database-url needs a full run id. Drop --database-url to look up a prefix over the admin API ('kici-admin execution list' shows full run ids).`);
|
|
30678
|
+
}
|
|
30594
30679
|
function printRunsTable(runs) {
|
|
30595
30680
|
if (runs.length === 0) {
|
|
30596
30681
|
console.log("No execution runs found.");
|
|
@@ -30662,8 +30747,9 @@ function registerExecutionCommands(program, getClient) {
|
|
|
30662
30747
|
printJsonOr(opts.json, result, (r) => printRunsTable(r.runs));
|
|
30663
30748
|
}
|
|
30664
30749
|
}));
|
|
30665
|
-
exec.command("show <runId>").description("Show one run + its jobs (by run_id)").option(DIRECT_DB_URL_FLAG, DIRECT_DB_URL_HELP).option("--json", "Emit JSON output").action(cliAction(async (
|
|
30750
|
+
exec.command("show <runId>").description("Show one run + its jobs (by run_id, or its first 4+ characters)").option(DIRECT_DB_URL_FLAG, DIRECT_DB_URL_HELP).option("--json", "Emit JSON output").action(cliAction(async (runRef, opts) => {
|
|
30666
30751
|
const dbUrl = resolveDirectDbUrl(opts.databaseUrl);
|
|
30752
|
+
const runId = await resolveExecutionRunRef(runRef, dbUrl ? void 0 : getClient());
|
|
30667
30753
|
const result = dbUrl ? await showExecutionRunDirect(dbUrl, { runId }) : await getClient().get(`/api/v1/admin/executions/${encodeURIComponent(runId)}`);
|
|
30668
30754
|
printJsonOr(opts.json, result, (r) => printRunShow(r.run, r.jobs));
|
|
30669
30755
|
}));
|
|
@@ -37130,7 +37216,7 @@ function routeRootArgs(root, args) {
|
|
|
37130
37216
|
* build entry; the fallback covers a source run (tests, `tsx`), the same shape
|
|
37131
37217
|
* `server.ts` and `standalone.ts` use for `ORCHESTRATOR_VERSION`.
|
|
37132
37218
|
*/
|
|
37133
|
-
const CLI_VERSION = "0.
|
|
37219
|
+
const CLI_VERSION = "0.21.0";
|
|
37134
37220
|
/**
|
|
37135
37221
|
* Root command whose option parsing first routes argv through
|
|
37136
37222
|
* {@link routeRootArgs}: a leading `--version` prints the CLI version, and an
|
|
@@ -37153,7 +37239,7 @@ var KiciAdminProgram = class extends Command {
|
|
|
37153
37239
|
*/
|
|
37154
37240
|
function buildProgram() {
|
|
37155
37241
|
const program = new KiciAdminProgram();
|
|
37156
|
-
program.name("kici-admin").description("KiCI orchestrator admin CLI for managing config, secrets, and tokens").version(CLI_VERSION, `-V, ${ROOT_VERSION_FLAG}`, "output the version number (--version also works before a command)").
|
|
37242
|
+
program.name("kici-admin").description("KiCI orchestrator admin CLI for managing config, secrets, and tokens").version(CLI_VERSION, `-V, ${ROOT_VERSION_FLAG}`, "output the version number (--version also works before a command)").addOption(new Option("-u, --url <url>", "Orchestrator URL").env("KICI_ADMIN_URL").default(DEFAULT_ADMIN_URL)).addOption(new Option("-t, --token <token>", "Admin API token").env("KICI_ADMIN_TOKEN"));
|
|
37157
37243
|
/** Lazy, so --help works without --token. */
|
|
37158
37244
|
function getClient() {
|
|
37159
37245
|
const opts = program.opts();
|
package/dist/db/types.d.ts
CHANGED
|
@@ -652,9 +652,9 @@ export interface ExecutionJobTable {
|
|
|
652
652
|
/** Job name (e.g. "test", "test[node-18]") */
|
|
653
653
|
job_name: string;
|
|
654
654
|
/**
|
|
655
|
-
* The workflow that defines the job. A run can span several workflows (
|
|
656
|
-
* fixture run
|
|
657
|
-
* name it. NULL on a row written before the column existed.
|
|
655
|
+
* The workflow that defines the job. A run can span several workflows (an older `kici`'s shared
|
|
656
|
+
* fixture run), so the run's own
|
|
657
|
+
* `workflow_name` does not name it. NULL on a row written before the column existed.
|
|
658
658
|
*/
|
|
659
659
|
workflow_name: ColumnType<string | null, string | null | undefined, string | null>;
|
|
660
660
|
/** Job status: an `ExecutionJobStatus` value (engine). */
|
package/dist/index.js
CHANGED
|
@@ -199,9 +199,10 @@ export interface WorkflowDispatchContext {
|
|
|
199
199
|
*/
|
|
200
200
|
runRegisteredBeforeDispatch?: boolean;
|
|
201
201
|
/**
|
|
202
|
-
* Called each time this dispatch registers the run with the tracker.
|
|
203
|
-
* fixture run dispatches several
|
|
204
|
-
* run
|
|
202
|
+
* Called each time this dispatch registers the run with the tracker. An older `kici`'s shared
|
|
203
|
+
* fixture run dispatches several
|
|
204
|
+
* workflows under one run id, and holds the run open from the first
|
|
205
|
+
* registration until every workflow has registered.
|
|
205
206
|
*/
|
|
206
207
|
onRunRegistered?: () => void;
|
|
207
208
|
/**
|
|
@@ -433,7 +434,8 @@ interface JobEnvEvalResult {
|
|
|
433
434
|
* Synthetic job-id prefix every needs-gate site stamps on a held-back job. The
|
|
434
435
|
* release path (`dispatchReadyJob` → `findSyntheticJobId` → `addJobsToRun`)
|
|
435
436
|
* keys on it, so it soundly recovers the gated set without threading a parallel
|
|
436
|
-
* list through both the single-orch and cluster paths. Owned by `db/synthetic-job-ids.ts` so the
|
|
437
|
+
* list through both the single-orch and cluster paths. Owned by `db/synthetic-job-ids.ts` so the
|
|
438
|
+
* `contexts/`
|
|
437
439
|
* concurrency sweep need not import this module.
|
|
438
440
|
*/
|
|
439
441
|
export { NEEDS_PENDING_JOB_ID_PREFIX };
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
* decorating secret resolver), and the fixture's `secrets` context mapping
|
|
21
21
|
* resolves into namespaced secrets;
|
|
22
22
|
* - direct workflow execution (bypass trigger matching) is supported.
|
|
23
|
+
*
|
|
24
|
+
* Like a webhook, it creates one run per matched workflow. A caller that
|
|
25
|
+
* follows a single run (no `acceptsMultipleRuns`) gets one shared run instead,
|
|
26
|
+
* and only when the workflows' job names cannot collide in it.
|
|
23
27
|
*/
|
|
24
28
|
import { type ProcessingDeps } from './processor.js';
|
|
25
29
|
import { type ActorPrincipal } from '@kici-dev/engine';
|
|
@@ -79,9 +83,18 @@ export interface TestTriggerInput {
|
|
|
79
83
|
* here against the matched workflow's lock dispatch descriptor.
|
|
80
84
|
*/
|
|
81
85
|
dispatchInputs?: Record<string, string>;
|
|
86
|
+
/**
|
|
87
|
+
* The caller follows every run in the result's `runs`. Without it, several
|
|
88
|
+
* matched workflows share one run, or the trigger is refused when their job
|
|
89
|
+
* names collide.
|
|
90
|
+
*/
|
|
91
|
+
acceptsMultipleRuns?: boolean;
|
|
82
92
|
}
|
|
83
93
|
interface TestTriggerResult {
|
|
94
|
+
/** The first run's id: the only run when the caller follows one. */
|
|
84
95
|
runId: string;
|
|
96
|
+
/** One entry per run, each executing one matched workflow. Absent for a shared run. */
|
|
97
|
+
runs?: TestTriggeredRun[];
|
|
85
98
|
status: 'accepted' | 'rejected';
|
|
86
99
|
reason?: string;
|
|
87
100
|
jobIds: string[];
|
|
@@ -100,9 +113,16 @@ export declare function repoIdentityFromInlineInput(input: TestTriggerInput): {
|
|
|
100
113
|
repoIdentifier: string;
|
|
101
114
|
provider: string;
|
|
102
115
|
};
|
|
116
|
+
/** One run the trigger created, with the matched workflow it executes. */
|
|
117
|
+
export interface TestTriggeredRun {
|
|
118
|
+
runId: string;
|
|
119
|
+
workflowName: string;
|
|
120
|
+
jobIds: string[];
|
|
121
|
+
}
|
|
103
122
|
/**
|
|
104
123
|
* Process a test trigger through the shared dispatch core: one
|
|
105
|
-
* `WorkflowDispatchContext` per matched workflow, into `dispatchMatchedWorkflow
|
|
124
|
+
* `WorkflowDispatchContext` per matched workflow, into `dispatchMatchedWorkflow`,
|
|
125
|
+
* each under its own run id.
|
|
106
126
|
*/
|
|
107
127
|
export declare function processTestTrigger(input: TestTriggerInput, deps: ProcessingDeps): Promise<TestTriggerResult>;
|
|
108
128
|
export {};
|
|
@@ -192,6 +192,8 @@ interface UpdateStepProgressOptions {
|
|
|
192
192
|
jobName: string;
|
|
193
193
|
stepIndex: number;
|
|
194
194
|
stepName: string;
|
|
195
|
+
/** The step's `step_type`, when the agent sent one. */
|
|
196
|
+
stepType?: string;
|
|
195
197
|
state: 'running' | 'success' | 'failed' | 'skipped' | 'cancelled';
|
|
196
198
|
durationMs?: number;
|
|
197
199
|
installationId?: number;
|
|
@@ -42,7 +42,19 @@ export interface StepProgressEntry {
|
|
|
42
42
|
name: string;
|
|
43
43
|
status: 'pending' | 'running' | 'success' | 'failed' | 'skipped' | 'cancelled';
|
|
44
44
|
durationMs?: number;
|
|
45
|
+
/** The step's stored index. An entry without one is read by its position (defensive). */
|
|
46
|
+
stepIndex?: number;
|
|
47
|
+
/** The step's `step_type`; places setup and init pseudo-steps first. */
|
|
48
|
+
stepType?: string;
|
|
45
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Record one step's progress: replace the entry for its index, or add it in display
|
|
52
|
+
* order. Pseudo-steps have high indices (init at 1_000_000 + n), so the list is keyed
|
|
53
|
+
* by index rather than position; a positional list would pad every gap up to it.
|
|
54
|
+
*/
|
|
55
|
+
export declare function applyStepProgress(steps: StepProgressEntry[], entry: StepProgressEntry & {
|
|
56
|
+
stepIndex: number;
|
|
57
|
+
}): StepProgressEntry[];
|
|
46
58
|
interface BuildCheckRunSummaryOptions {
|
|
47
59
|
jobName: string;
|
|
48
60
|
stepResults: StepResultData[];
|
|
@@ -285,9 +285,9 @@ export interface TrackedJobRow {
|
|
|
285
285
|
jobId: string;
|
|
286
286
|
jobName: string;
|
|
287
287
|
/**
|
|
288
|
-
* The workflow that defines the job.
|
|
289
|
-
*
|
|
290
|
-
* name it. `onExecutionStarted` stamps its own workflow on a job without one;
|
|
288
|
+
* The workflow that defines the job. An older `kici`'s shared fixture run holds every matched
|
|
289
|
+
* workflow, so the run's own workflow
|
|
290
|
+
* does not name it. `onExecutionStarted` stamps its own workflow on a job without one;
|
|
291
291
|
* `addJobsToRun` inherits it from the placeholder the job replaces.
|
|
292
292
|
*/
|
|
293
293
|
workflowName?: string;
|
|
@@ -830,6 +830,13 @@ export declare class ExecutionTracker {
|
|
|
830
830
|
dispatchedByFailureLifecycle?: boolean;
|
|
831
831
|
/** Re-run lineage, when this run re-runs another. */
|
|
832
832
|
rerunLineage?: RerunLineage;
|
|
833
|
+
/**
|
|
834
|
+
* Set for a `kici run` test run, so its init failure stays readable on the
|
|
835
|
+
* test-run status and log routes, which serve only test runs.
|
|
836
|
+
*/
|
|
837
|
+
testRun?: {
|
|
838
|
+
fixtureId: string;
|
|
839
|
+
};
|
|
833
840
|
}): Promise<void>;
|
|
834
841
|
/**
|
|
835
842
|
* Write one `failed` execution_runs row for a global eval round that never
|
|
@@ -946,6 +953,10 @@ export declare class ExecutionTracker {
|
|
|
946
953
|
* {@link ExecutionTracker.recordGlobalEvalRoundFailureRun}).
|
|
947
954
|
*/
|
|
948
955
|
dispatchRoutingKey?: string;
|
|
956
|
+
/** Set for a `kici run` test run, as on {@link ExecutionTracker.recordInitFailureRun}. */
|
|
957
|
+
testRun?: {
|
|
958
|
+
fixtureId: string;
|
|
959
|
+
};
|
|
949
960
|
}): Promise<void>;
|
|
950
961
|
/**
|
|
951
962
|
* Claim a held global evaluation round for release (`held` → `pending`).
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The workflows a run spans, read from its job rows.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* An older `kici`'s shared fixture run holds every
|
|
5
|
+
* matched workflow. `execution_runs.workflow_name` holds the first of them; each job row
|
|
6
6
|
* records its own in `execution_jobs.workflow_name`.
|
|
7
7
|
*/
|
|
8
8
|
import type { Kysely } from 'kysely';
|
|
@@ -3,26 +3,28 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Sorting `execution_steps` by `step_index` alone puts a pseudo-step where its index landed,
|
|
5
5
|
* not where it ran. That suits the cache pseudo-steps (indices above the real steps, mostly
|
|
6
|
-
* bracketing the work), but a job's image build
|
|
6
|
+
* bracketing the work), but a job's image build and its init commands run strictly FIRST.
|
|
7
7
|
*
|
|
8
8
|
* The index cannot express this: `step.status` rejects a negative index (an agent sending one
|
|
9
9
|
* to an older orchestrator is disconnected mid-job), and the check-run reporter
|
|
10
10
|
* (`steps[stepIndex]`) and the dashboard (log fetching gated on `stepIndex >= 0`) treat a
|
|
11
|
-
* negative index as absent. So the ordering is expressed by TYPE,
|
|
11
|
+
* negative index as absent. So the ordering is expressed by TYPE, as the engine's
|
|
12
|
+
* `StepPhase`, here, once.
|
|
12
13
|
*/
|
|
13
14
|
import { type Expression } from 'kysely';
|
|
14
15
|
/**
|
|
15
|
-
* Sort key placing setup pseudo-steps
|
|
16
|
-
* the `step_index` sort:
|
|
16
|
+
* Sort key placing steps by `StepPhase`: setup pseudo-steps, then init commands, then the
|
|
17
|
+
* job's steps. Apply BEFORE the `step_index` sort:
|
|
17
18
|
*
|
|
18
19
|
* ```ts
|
|
19
20
|
* qb.orderBy(setupStepsFirst()).orderBy('step_index', 'asc')
|
|
20
21
|
* ```
|
|
21
22
|
*
|
|
22
23
|
* Untyped steps and every real step share one key, so `step_index` alone orders each group.
|
|
24
|
+
* The init pattern matches `initStepIndex`: the prefix followed by digits only.
|
|
23
25
|
*/
|
|
24
26
|
export declare function setupStepsFirst(): Expression<number>;
|
|
25
|
-
/** In-memory equivalent, kept beside the SQL so the two agree on
|
|
27
|
+
/** In-memory equivalent over DB rows, kept beside the SQL so the two agree on the phases. */
|
|
26
28
|
export declare function compareStepsForDisplay(a: {
|
|
27
29
|
step_type?: string | null;
|
|
28
30
|
step_index: number;
|
|
@@ -30,4 +32,6 @@ export declare function compareStepsForDisplay(a: {
|
|
|
30
32
|
step_type?: string | null;
|
|
31
33
|
step_index: number;
|
|
32
34
|
}): number;
|
|
35
|
+
/** {@link compareStepsForDisplay} over untyped rows, such as cold-store rows read back from S3. */
|
|
36
|
+
export declare function compareStepRowsForDisplay(a: Record<string, unknown>, b: Record<string, unknown>): number;
|
|
33
37
|
//# sourceMappingURL=step-display-order.d.ts.map
|