@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.
@@ -5,7 +5,7 @@ __cjs_dirname(__filename);
5
5
  import "node:module";
6
6
  import "@kici-dev/shared/load-service-env-file";
7
7
  import { AgentDeliveryMode, AgentPlatform, BaseColdStore, ChunkLru, ChunkRequestWaiter, CommandError, PgTableAdapterBase, addLogsToArchive, applyProxyKeepAliveTimeouts, chunkBuffer, closeDatabase, computeBackoffDelay, computeMigrationsHash, createDb, createHealthRoutes, createLogger, createMeter, createMetricsRoutes, createPool, createS3Client, decrypt, decryptJson, depCacheKeyOf, deriveKey, encodeKeySegment, encrypt, enrichRequestContext, formatDuration, getPrometheusExporter, getReconnectDelay, getRequestContext, guardStartup, initTelemetry, isPgUniqueViolation, logger, parseDatabaseUrl, redactConfig, requestContext, retryOnBrokenConnection, serializeError, setServiceName, setupGracefulShutdown, shQuote, sha256, stderrTail, stopHttpServer, storeMigrationContentHash, toCommandError, toErrorMessage, trackOpenSockets, validateRequiredTools } from "@kici-dev/shared";
8
- import { ACTOR_AGENT_SUFFIX, AGENT_API_REQUEST_TIMEOUT_MS, AGENT_FEATURE_LABELS, ALLOWED_SYSTEM_VARS, ARTIFACT_NAME_MAX_LENGTH, AccessLogAction, AccessLogOutcome, AccessLogSource, AccessLogTargetType, ActorType, AgentFailureCategory, ApprovalDecision, ArtifactCompleteAckOutcome, ArtifactDownloadOutcome, ArtifactUploadOutcome, AttestationOrigin, BREAKING_FLOOR, CANONICAL_STATUSES, CONTAINER_BUILD_RUNTIME_LABEL, CacheRefScope, CheckMode, CheckRunConclusion, CheckStepOutcome, CiTrustLevel, ConcurrencyStrategy, ContextDeleteErrorCode, ContextGateRejectReason, ContextType, DASHBOARD_REQUEST_TYPE_SET, DEFAULT_APPROVAL_EXPIRY_SECONDS, DEFAULT_CONCURRENCY_STRATEGY, DEFAULT_HOLD_EXPIRY_SECONDS, DEFERRED_PATHS_SUMMARY_SUFFIX, EventLogSource, EventLogStatus, ExecutionJobStatus, ExecutionRunStatus, ExecutionStepStatus, FanoutCause, FanoutError, FleetHostWriteRefusal, ForkPolicy, GLOBAL_EVAL_SKIPS_RESULT_AWARE_LABEL, HeldRunQueueType, HeldRunStatus, HoldScope, HoldType, ID_TOKEN_CLAIM_NAMES, INIT_LABEL, INIT_RUNNER_ROLE_LABEL, INSTALL_JOB_ID_PREFIX, INVALID_JOIN_REQUEST_MESSAGE, InitFailureCategory, InventorySelectorSchema, JOB_IMAGE_RUNTIME_LABEL, JOIN_PROTOCOL_UPGRADE_TOKEN, JOIN_PROTOCOL_V1_REMOVED_MESSAGE, JobRejectReason, JoinErrorCode, JoinRequestKind, KICI_AGENT_ENV_PREFIX, KICI_EVENT_NAME_PREFIX, KNOWN_ROLES, LabelMatcher, LockFileParseError, LogStream, MAX_APPROVAL_EXPIRY_SECONDS, MIN_APPROVAL_EXPIRY_SECONDS, MIN_PROTOCOL_VERSION, MinimumTrustSchema, ORCH_AGENT_CAPABILITIES, OWN_INGRESS_MODES, OrchLogPhase, OrchRole, OrchestratorMode, PEER_MUTUAL_AUTH_REQUIRED_REASON, PLATFORM_CONNECTED_MODES, PLATFORM_TO_ORCH_RECOGNIZED_TYPES, PRIVILEGED_ROOT_LABEL, PROTOCOL_VERSION, PayloadOmittedReason, PeerAuthMode, PeerAuthScheme, PeerForgetOutcome, RegisterableTriggerType, RunFailureClass, RuntimeFact, SCALER_EVENT_NAMES, SCHEMA_VERSION, SECURITY_HOLD_JOB_IDS, SSH_TRANSPORT_CAPABILITY, ScaleDownReason, ScaleDownReason as ScaleDownReason$1, ScalerBackendType, ScalerEventType, ScalerEventType as ScalerEventType$1, ScalerOrphansAction, ScalerReloadOutcome, ScalerScaleDownPayload, ScalerScaleUpPayload, ScalerVmStatus, ScalerVmStopOutcome, ScalerVmTracker, SetupStepType, SourceOrigin, SourceSubtype, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, TEST_RUN_LOG_CURSOR_START, TRIGGER_EVENT_TYPES, TimeoutReason, TraceCheck, TriggerSource, TrustTierSchema, UnavailablePathsTrace, VariantKind, WS_CLOSE_AGENT_AUTH_FAILED, WS_CLOSE_AUTH_TIMEOUT, WS_CLOSE_DISPATCH_ACK_TIMEOUT, WS_CLOSE_GOING_AWAY, WS_CLOSE_HEARTBEAT_TIMEOUT, WS_CLOSE_INTERNAL_ERROR, WS_CLOSE_INVALID_MESSAGE, WS_CLOSE_PLAN_LIMIT, WS_CLOSE_PROTOCOL_ERROR, WS_CLOSE_SUPERSEDED_BY_RECONNECT, WS_CLOSE_UNAUTHORIZED, WS_MAX_PAYLOAD_BYTES, WebhookRelayResult, WsRateLimiter, accessLogWarmSqlCase, agentAuthRequestSchema, agentLabelOf, agentToOrchestratorMessageSchema, agentTypeLabel, appendChecks, approvalTimeoutSecondsSchema, artifactInvalidNameError, assertValidSecretKey, attestationVerifyStatusSchema, buildEventClaims, buildIdTokenSubject, buildJoinRefusal, buildTrustedPassthroughEnv, canonicalizeCapability, canonicalizeLabel, canonicalizeLabelSet, canonicalizeLabels, canonicalizeMatcher, checkArtifactName, classifyJoinRequest, coerceDispatchInputs, collectDiscriminatorTypes, createContentRequirementsTraceEntry, createDispatchFailureTraceEntry, createGlobalFilterTraceEntry, createWorkflowDecision, deriveOsArchLabels, diffRangeKindSchema, fanoutEnvelopeFields, flattenActor, formatLabelMatcher, getAccessLogColdDays, getSecretAuditLogColdDays, githubIngressPath, githubWebhookPath, globalEvalRoundResultSchema, hostLabel, hostSatisfiesTarget, hostToScalerPlatform, installGateJobId, isFailureStatus, isKnownCapability, isLockDynamicJobFn, isLockParallelStep, isLockStaticJob, isPullRequestFamilyTriggerEvent, isSecurityHoldJobId, isSelfReportedLabel, joinRequestSchema, logPullPlatformToOrchSchema, matchAllWorkflows, matchScopePattern, matchWorkflowsForEvent, matcherSatisfiedBy, materializeFanout, materializeResolvedHosts, materializeResolvedMatrix, matrixEnvelopeFields, mergeOrderedMaps, minAccessLogWarmDays, minSecretAuditLogWarmDays, partitionMatchers, peerAuthRequestSchema, peerFromPeerMessageSchema, peerHelloResponseSchema, peerHelloSchema, platformToOsArchLabels, platformToTaints, reservedEventNamePrefix, resolveDiffRange, resolveRoleLabels, resolveScheduleInputs, resolveSecretsWithProvenance, resourceRequestNestedSchema, runtimeLabel, scalerAgentLabels, scalerLabel, scalerPlatformSchema, scheduleTriggerKey, secretAuditLogWarmSqlCase, shouldRecordAccess, shouldRecordSecretResolve, sleep, stringifyActor, testRelayTriggerRequestSchema, testRelayUploadsInitRequestSchema, toIsoString, truncateDecisionsToByteBudget, truncateReasonText, trustPolicyUpdateSchema, trustedContributorHoldReason, validateScopeName, validateSecretKey, wrapUntrusted } from "@kici-dev/engine";
8
+ import { ACTOR_AGENT_SUFFIX, AGENT_API_REQUEST_TIMEOUT_MS, AGENT_FEATURE_LABELS, ALLOWED_SYSTEM_VARS, ARTIFACT_NAME_MAX_LENGTH, AccessLogAction, AccessLogOutcome, AccessLogSource, AccessLogTargetType, ActorType, AgentFailureCategory, ApprovalDecision, ArtifactCompleteAckOutcome, ArtifactDownloadOutcome, ArtifactUploadOutcome, AttestationOrigin, BREAKING_FLOOR, CANONICAL_STATUSES, CONTAINER_BUILD_RUNTIME_LABEL, CacheRefScope, CheckMode, CheckRunConclusion, CheckStepOutcome, CiTrustLevel, ConcurrencyStrategy, ContextDeleteErrorCode, ContextGateRejectReason, ContextType, DASHBOARD_REQUEST_TYPE_SET, DEFAULT_APPROVAL_EXPIRY_SECONDS, DEFAULT_CONCURRENCY_STRATEGY, DEFAULT_HOLD_EXPIRY_SECONDS, DEFERRED_PATHS_SUMMARY_SUFFIX, EventLogSource, EventLogStatus, ExecutionJobStatus, ExecutionRunStatus, ExecutionStepStatus, FanoutCause, FanoutError, FleetHostWriteRefusal, ForkPolicy, GLOBAL_EVAL_SKIPS_RESULT_AWARE_LABEL, HeldRunQueueType, HeldRunStatus, HoldScope, HoldType, ID_TOKEN_CLAIM_NAMES, INIT_LABEL, INIT_RUNNER_ROLE_LABEL, INIT_STEP_TYPE_PREFIX, INSTALL_JOB_ID_PREFIX, INVALID_JOIN_REQUEST_MESSAGE, InitFailureCategory, InventorySelectorSchema, JOB_IMAGE_RUNTIME_LABEL, JOIN_PROTOCOL_UPGRADE_TOKEN, JOIN_PROTOCOL_V1_REMOVED_MESSAGE, JobRejectReason, JoinErrorCode, JoinRequestKind, KICI_AGENT_ENV_PREFIX, KICI_EVENT_NAME_PREFIX, KNOWN_ROLES, LabelMatcher, LockFileParseError, LogStream, MAX_APPROVAL_EXPIRY_SECONDS, MIN_APPROVAL_EXPIRY_SECONDS, MIN_PROTOCOL_VERSION, MinimumTrustSchema, ORCH_AGENT_CAPABILITIES, OWN_INGRESS_MODES, OrchLogPhase, OrchRole, OrchestratorMode, PEER_MUTUAL_AUTH_REQUIRED_REASON, PLATFORM_CONNECTED_MODES, PLATFORM_TO_ORCH_RECOGNIZED_TYPES, PRIVILEGED_ROOT_LABEL, PROTOCOL_VERSION, PayloadOmittedReason, PeerAuthMode, PeerAuthScheme, PeerForgetOutcome, RegisterableTriggerType, RunFailureClass, RuntimeFact, SCALER_EVENT_NAMES, SCHEMA_VERSION, SECURITY_HOLD_JOB_IDS, SSH_TRANSPORT_CAPABILITY, ScaleDownReason, ScaleDownReason as ScaleDownReason$1, ScalerBackendType, ScalerEventType, ScalerEventType as ScalerEventType$1, ScalerOrphansAction, ScalerReloadOutcome, ScalerScaleDownPayload, ScalerScaleUpPayload, ScalerVmStatus, ScalerVmStopOutcome, ScalerVmTracker, SetupStepType, SourceOrigin, SourceSubtype, StepPhase, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, TEST_RUN_LOG_CURSOR_START, TRIGGER_EVENT_TYPES, TimeoutReason, TraceCheck, TriggerSource, TrustTierSchema, UnavailablePathsTrace, VariantKind, WS_CLOSE_AGENT_AUTH_FAILED, WS_CLOSE_AUTH_TIMEOUT, WS_CLOSE_DISPATCH_ACK_TIMEOUT, WS_CLOSE_GOING_AWAY, WS_CLOSE_HEARTBEAT_TIMEOUT, WS_CLOSE_INTERNAL_ERROR, WS_CLOSE_INVALID_MESSAGE, WS_CLOSE_PLAN_LIMIT, WS_CLOSE_PROTOCOL_ERROR, WS_CLOSE_SUPERSEDED_BY_RECONNECT, WS_CLOSE_UNAUTHORIZED, WS_MAX_PAYLOAD_BYTES, WebhookRelayResult, WsRateLimiter, accessLogWarmSqlCase, agentAuthRequestSchema, agentLabelOf, agentToOrchestratorMessageSchema, agentTypeLabel, appendChecks, approvalTimeoutSecondsSchema, artifactInvalidNameError, assertValidSecretKey, attestationVerifyStatusSchema, buildEventClaims, buildIdTokenSubject, buildJoinRefusal, buildTrustedPassthroughEnv, canonicalizeCapability, canonicalizeLabel, canonicalizeLabelSet, canonicalizeLabels, canonicalizeMatcher, checkArtifactName, classifyJoinRequest, coerceDispatchInputs, collectDiscriminatorTypes, compareStepsByPhase, createContentRequirementsTraceEntry, createDispatchFailureTraceEntry, createGlobalFilterTraceEntry, createWorkflowDecision, deriveOsArchLabels, diffRangeKindSchema, fanoutEnvelopeFields, flattenActor, formatLabelMatcher, getAccessLogColdDays, getSecretAuditLogColdDays, githubIngressPath, githubWebhookPath, globalEvalRoundResultSchema, hostLabel, hostSatisfiesTarget, hostToScalerPlatform, initStepIndex, installGateJobId, isFailureStatus, isKnownCapability, isLockDynamicJobFn, isLockParallelStep, isLockStaticJob, isPullRequestFamilyTriggerEvent, isRunId, isSecurityHoldJobId, isSelfReportedLabel, joinRequestSchema, logPullPlatformToOrchSchema, matchAllWorkflows, matchScopePattern, matchWorkflowsForEvent, matcherSatisfiedBy, materializeFanout, materializeResolvedHosts, materializeResolvedMatrix, matrixEnvelopeFields, mergeOrderedMaps, minAccessLogWarmDays, minSecretAuditLogWarmDays, parseRunIdRef, partitionMatchers, peerAuthRequestSchema, peerFromPeerMessageSchema, peerHelloResponseSchema, peerHelloSchema, platformToOsArchLabels, platformToTaints, reservedEventNamePrefix, resolveDiffRange, resolveRoleLabels, resolveScheduleInputs, resolveSecretsWithProvenance, resourceRequestNestedSchema, runIdPrefixRange, runtimeLabel, scalerAgentLabels, scalerLabel, scalerPlatformSchema, scheduleTriggerKey, secretAuditLogWarmSqlCase, shouldRecordAccess, shouldRecordSecretResolve, sleep, stepPhase, stepTypeOf, stringifyActor, testRelayTriggerRequestSchema, testRelayUploadsInitRequestSchema, toIsoString, truncateDecisionsToByteBudget, truncateReasonText, trustPolicyUpdateSchema, trustedContributorHoldReason, validateScopeName, validateSecretKey, wrapUntrusted } from "@kici-dev/engine";
9
9
  import { z } from "zod";
10
10
  import { X509Certificate, createCipheriv, createDecipheriv, createHash, createHmac, createPrivateKey, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
11
11
  import { sql } from "kysely";
@@ -5979,7 +5979,7 @@ var init_peer_client = __esmMin((() => {
5979
5979
  PeerDialOrigin["Discovered"] = "discovered";
5980
5980
  return PeerDialOrigin;
5981
5981
  }({});
5982
- SOFTWARE_VERSION$1 = "0.20.1";
5982
+ SOFTWARE_VERSION$1 = "0.21.0";
5983
5983
  PeerClient$1 = class {
5984
5984
  ws = null;
5985
5985
  _state = "disconnected";
@@ -8450,7 +8450,7 @@ var init_peer_handler = __esmMin((() => {
8450
8450
  init_join_token();
8451
8451
  init_run_detached();
8452
8452
  logger$154 = createLogger({ prefix: "peer-handler" });
8453
- SOFTWARE_VERSION = "0.20.1";
8453
+ SOFTWARE_VERSION = "0.21.0";
8454
8454
  ADVERTISED_PEER_AUTH_SCHEMES = [PeerAuthScheme.enum["mutual-v2"]];
8455
8455
  MAX_TOKEN_CANDIDATES = 8;
8456
8456
  RATE_LIMIT_MAX = 5;
@@ -12559,6 +12559,17 @@ var init_reduced_privilege_note = __esmMin((() => {
12559
12559
  * - Size safety: 65535 byte limit with progressive truncation
12560
12560
  */
12561
12561
  /**
12562
+ * Record one step's progress: replace the entry for its index, or add it in display
12563
+ * order. Pseudo-steps have high indices (init at 1_000_000 + n), so the list is keyed
12564
+ * by index rather than position; a positional list would pad every gap up to it.
12565
+ */
12566
+ function applyStepProgress(steps, entry) {
12567
+ return [...steps.map((s, position) => ({
12568
+ ...s,
12569
+ stepIndex: s.stepIndex ?? position
12570
+ })).filter((s) => s.stepIndex !== entry.stepIndex), entry].sort(compareStepsByPhase);
12571
+ }
12572
+ /**
12562
12573
  * Cut `summary` to fit the API's cap, whatever a caller wrapped around it. The last line of
12563
12574
  * defence: `reservedBytes` keeps a summary readable by dropping log tiers first.
12564
12575
  */
@@ -13543,27 +13554,13 @@ var init_check_run_reporter = __esmMin((() => {
13543
13554
  if (!checkRunId) return;
13544
13555
  if (this.terminalSent.has(key)) return;
13545
13556
  this.trackRunKey(opts.runId, key);
13546
- let steps = this.stepProgress.get(key);
13547
- if (!steps) {
13548
- steps = [];
13549
- this.stepProgress.set(key, steps);
13550
- }
13551
- if (opts.stepIndex < steps.length) steps[opts.stepIndex] = {
13557
+ const steps = applyStepProgress(this.stepProgress.get(key) ?? [], {
13552
13558
  name: opts.stepName,
13553
13559
  status: opts.state,
13554
- durationMs: opts.durationMs
13555
- };
13556
- else {
13557
- while (steps.length < opts.stepIndex) steps.push({
13558
- name: `Step ${steps.length}`,
13559
- status: "pending"
13560
- });
13561
- steps.push({
13562
- name: opts.stepName,
13563
- status: opts.state,
13564
- durationMs: opts.durationMs
13565
- });
13566
- }
13560
+ durationMs: opts.durationMs,
13561
+ stepIndex: opts.stepIndex,
13562
+ ...opts.stepType && { stepType: opts.stepType }
13563
+ });
13567
13564
  await this.persistStepProgress(key, steps, opts.runId);
13568
13565
  if (opts.state === ExecutionStepStatus.enum.running && !this.inProgressSent.get(key)) {
13569
13566
  await this.persistInProgressSent(key, opts.runId);
@@ -18772,7 +18769,7 @@ var init_execution_tracker = __esmMin((() => {
18772
18769
  const run = this.runs.get(runId);
18773
18770
  const prefix = `needs-pending-${jobName}-`;
18774
18771
  if (run) {
18775
- for (const key of run.jobs.keys()) if (key.startsWith(prefix)) return key;
18772
+ for (const [key, job] of run.jobs) if (key.startsWith(prefix) && job.name === jobName) return key;
18776
18773
  }
18777
18774
  return (await this.db.selectFrom("execution_jobs").select("job_id").where("run_id", "=", runId).where("job_name", "=", jobName).where("job_id", "like", `${prefix}%`).executeTakeFirst())?.job_id;
18778
18775
  }
@@ -19846,6 +19843,10 @@ var init_execution_tracker = __esmMin((() => {
19846
19843
  async recordInitFailureRun(args) {
19847
19844
  const now = /* @__PURE__ */ new Date();
19848
19845
  const customerId = await this.resolveCustomerId(args.routingKey);
19846
+ const testRunColumns = args.testRun ? {
19847
+ is_test_run: true,
19848
+ fixture_id: args.testRun.fixtureId
19849
+ } : {};
19849
19850
  const crossRepoWorkflowRepo = crossRepoWorkflowRepoOf(args);
19850
19851
  if (!guardedWriteApplied(await this.db.insertInto("execution_runs").values({
19851
19852
  run_id: args.runId,
@@ -19867,7 +19868,8 @@ var init_execution_tracker = __esmMin((() => {
19867
19868
  init_failure: JSON.stringify(args.initFailure),
19868
19869
  ...inheritedChainDepth(args.chainDepth),
19869
19870
  ...rerunLineageColumns(args.rerunLineage),
19870
- ...crossRepoWorkflowRepo && { workflow_repo_identifier: crossRepoWorkflowRepo }
19871
+ ...crossRepoWorkflowRepo && { workflow_repo_identifier: crossRepoWorkflowRepo },
19872
+ ...testRunColumns
19871
19873
  }).onConflict((oc) => oc.column("run_id").doUpdateSet({
19872
19874
  completed_at: now,
19873
19875
  duration_ms: sql`extract(epoch from (${now} - execution_runs.started_at)) * 1000`,
@@ -19876,7 +19878,8 @@ var init_execution_tracker = __esmMin((() => {
19876
19878
  failure_class: RunFailureClass.enum.never_started,
19877
19879
  init_failure: JSON.stringify(args.initFailure),
19878
19880
  ...inheritedChainDepth(args.chainDepth),
19879
- ...crossRepoWorkflowRepo && { workflow_repo_identifier: crossRepoWorkflowRepo }
19881
+ ...crossRepoWorkflowRepo && { workflow_repo_identifier: crossRepoWorkflowRepo },
19882
+ ...testRunColumns
19880
19883
  }).where("execution_runs.status", "not in", [...TERMINAL_RUN_STATES])).executeTakeFirst())) {
19881
19884
  logger$129.debug("Init-failure record skipped: run already terminal in DB", { runId: args.runId });
19882
19885
  return;
@@ -19981,6 +19984,10 @@ var init_execution_tracker = __esmMin((() => {
19981
19984
  */
19982
19985
  async recordRunHeld(args) {
19983
19986
  const now = /* @__PURE__ */ new Date();
19987
+ const testRunColumns = args.testRun ? {
19988
+ is_test_run: true,
19989
+ fixture_id: args.testRun.fixtureId
19990
+ } : {};
19984
19991
  const customerId = await this.resolveCustomerId(args.routingKey);
19985
19992
  const crossRepoProvenance = crossRepoProvenanceOf(args.repoIdentifier, {
19986
19993
  identifier: args.workflowRepoIdentifier,
@@ -20008,11 +20015,13 @@ var init_execution_tracker = __esmMin((() => {
20008
20015
  ...rerunLineageColumns(args.rerunLineage),
20009
20016
  ...crossRepoProvenance && workflowProvenanceColumns(crossRepoProvenance),
20010
20017
  ...args.isGlobalEvalRound === true && { is_global_eval_round: true },
20011
- ...args.dispatchRoutingKey != null && args.dispatchRoutingKey !== args.routingKey && { dispatch_routing_key: args.dispatchRoutingKey }
20018
+ ...args.dispatchRoutingKey != null && args.dispatchRoutingKey !== args.routingKey && { dispatch_routing_key: args.dispatchRoutingKey },
20019
+ ...testRunColumns
20012
20020
  }).onConflict((oc) => oc.column("run_id").doUpdateSet({
20013
20021
  status: ExecutionRunStatus.enum.held,
20014
20022
  ...args.contextName && { context: args.contextName },
20015
20023
  ...args.prNumber != null && { pr_number: args.prNumber },
20024
+ ...testRunColumns,
20016
20025
  ...inheritedChainDepth(args.chainDepth),
20017
20026
  ...crossRepoProvenance && {
20018
20027
  workflow_repo_identifier: crossRepoProvenance.identifier,
@@ -20278,7 +20287,8 @@ var init_execution_tracker = __esmMin((() => {
20278
20287
  log_path: logPath,
20279
20288
  routing_key: this.runs.get(runId)?.routingKey ?? null
20280
20289
  };
20281
- if (data?.stepType && typeof data.stepType === "string") values.step_type = data.stepType;
20290
+ const stepType = stepTypeOf(data);
20291
+ if (stepType) values.step_type = stepType;
20282
20292
  if (state === ExecutionStepStatus.enum.running) values.started_at = now;
20283
20293
  if (TERMINAL_JOB_STATES.has(state)) {
20284
20294
  values.completed_at = now;
@@ -33852,7 +33862,8 @@ async function recordInitFailureFromSkip(args) {
33852
33862
  },
33853
33863
  triggerEvent: dispatchTriggerEvent(ctx),
33854
33864
  commitMessage: extractCommitMessage(setup.info.event, setup.info.payload),
33855
- ...preDispatchRunProvenance(ctx)
33865
+ ...preDispatchRunProvenance(ctx),
33866
+ ...ctx.testRun && { testRun: ctx.testRun }
33856
33867
  });
33857
33868
  await completeChecksForUndispatchedRun({
33858
33869
  ctx,
@@ -33903,7 +33914,8 @@ async function holdWorkflowForInstallGate(args) {
33903
33914
  commitMessage: extractCommitMessage(setup.info.event, setup.info.payload),
33904
33915
  workflowSha: ctx.global?.workflowSha ?? null,
33905
33916
  workflowBranch: ctx.global?.workflowBranch ?? null,
33906
- ...preDispatchRunProvenance(ctx)
33917
+ ...preDispatchRunProvenance(ctx),
33918
+ ...ctx.testRun && { testRun: ctx.testRun }
33907
33919
  });
33908
33920
  if (deps.heldRunStore) await deps.heldRunStore.createHold(ctx.resolvedOrgId, {
33909
33921
  runId,
@@ -33979,7 +33991,8 @@ async function holdRunForSecurityPolicy(args) {
33979
33991
  prNumber: event.prNumber ?? null,
33980
33992
  workflowSha: ctx.global?.workflowSha ?? null,
33981
33993
  workflowBranch: ctx.global?.workflowBranch ?? null,
33982
- ...preDispatchRunProvenance(ctx)
33994
+ ...preDispatchRunProvenance(ctx),
33995
+ ...ctx.testRun && { testRun: ctx.testRun }
33983
33996
  });
33984
33997
  let heldRow;
33985
33998
  if (deps.heldRunStore) {
@@ -62761,7 +62774,9 @@ function createAdminQueueExecutionRoutes(deps) {
62761
62774
  });
62762
62775
  app.get("/executions/:runId", async (c) => {
62763
62776
  deps.rbac.requirePermission(c.get("role"), "secret.read");
62764
- const runId = c.req.param("runId");
62777
+ const rawRunId = c.req.param("runId");
62778
+ if (!isRunId(rawRunId)) return c.json({ error: `Invalid run id '${rawRunId}': a run id is a full UUID.` }, 400);
62779
+ const runId = rawRunId.toLowerCase();
62765
62780
  const runRow = await sql`
62766
62781
  SELECT id, run_id, workflow_name, status, provider, repo_identifier,
62767
62782
  ref, sha, routing_key, context, trust_tier, created_at,
@@ -63663,6 +63678,10 @@ var init_decorating_secret_resolver = __esmMin((() => {
63663
63678
  * decorating secret resolver), and the fixture's `secrets` context mapping
63664
63679
  * resolves into namespaced secrets;
63665
63680
  * - direct workflow execution (bypass trigger matching) is supported.
63681
+ *
63682
+ * Like a webhook, it creates one run per matched workflow. A caller that
63683
+ * follows a single run (no `acceptsMultipleRuns`) gets one shared run instead,
63684
+ * and only when the workflows' job names cannot collide in it.
63666
63685
  */
63667
63686
  /**
63668
63687
  * Repo identity for an inline-lock (local working tree) run. Derived from the
@@ -63881,9 +63900,90 @@ function buildTestDispatchContext(shared, workflow, decision, runId) {
63881
63900
  ...Object.keys(shared.runWideFlatSecrets).length > 0 && { runWideFlatSecrets: shared.runWideFlatSecrets }
63882
63901
  };
63883
63902
  }
63903
+ /** The static job names a workflow defines; null when it has a dynamic job, whose names are unknown until it runs. */
63904
+ function staticJobNames(workflow) {
63905
+ const names = [];
63906
+ for (const job of workflow.jobs) {
63907
+ if (job._type !== "static") return null;
63908
+ names.push(job.name);
63909
+ }
63910
+ return names;
63911
+ }
63912
+ /**
63913
+ * The job names two or more matched workflows define, sorted. Null when a
63914
+ * matched workflow has a dynamic job, so the names cannot be compared before
63915
+ * the run.
63916
+ */
63917
+ function sharedJobNames(fullLockFile, matchedDecisions) {
63918
+ const owners = /* @__PURE__ */ new Map();
63919
+ for (const decision of matchedDecisions) {
63920
+ const workflow = fullLockFile.workflows.find((w) => w.name === decision.workflowName);
63921
+ if (!workflow) continue;
63922
+ const names = staticJobNames(workflow);
63923
+ if (names === null) return null;
63924
+ for (const name of new Set(names)) owners.set(name, (owners.get(name) ?? 0) + 1);
63925
+ }
63926
+ return [...owners.entries()].filter(([, count]) => count > 1).map(([name]) => name).sort();
63927
+ }
63928
+ /**
63929
+ * Split the matched workflows into runs: one run per workflow, as every other
63930
+ * run producer does.
63931
+ *
63932
+ * A caller that does not set `acceptsMultipleRuns` follows only `runId`. For
63933
+ * it, several matched workflows share one run when their job names cannot
63934
+ * collide (a run keys its needs edges, pending contexts and logs by job name),
63935
+ * and the trigger is refused when they can.
63936
+ */
63937
+ function planTestRuns(input, fullLockFile, matchedDecisions, firstRunId) {
63938
+ if (input.acceptsMultipleRuns || matchedDecisions.length === 1) return matchedDecisions.map((decision, i) => ({
63939
+ runId: i === 0 ? firstRunId : randomUUID(),
63940
+ decisions: [decision]
63941
+ }));
63942
+ const shared = sharedJobNames(fullLockFile, matchedDecisions);
63943
+ if (shared === null || shared.length > 0) return { rejected: `This fixture matches several workflows (${matchedDecisions.map((d) => d.workflowName).join(", ")}) and ${shared === null ? "one of them defines dynamic jobs, whose names cannot be checked before the run" : `they share job names (${shared.join(", ")})`}. This kici version follows a single run, so upgrade kici to run each workflow as its own run, or pass --workflow <name> to run one workflow.` };
63944
+ return [{
63945
+ runId: firstRunId,
63946
+ decisions: matchedDecisions
63947
+ }];
63948
+ }
63949
+ /**
63950
+ * Dispatch one planned run. A run of several workflows (the shared run of a
63951
+ * caller without `acceptsMultipleRuns`) is held open from its first
63952
+ * registration until its last workflow has registered: a workflow whose jobs
63953
+ * all end before the next one registers would otherwise complete the run, and
63954
+ * the next workflow would start it over.
63955
+ */
63956
+ async function dispatchPlannedRun(shared, deps, planned) {
63957
+ const { runId, decisions } = planned;
63958
+ const out = {
63959
+ jobIds: [],
63960
+ deferredJobCount: 0,
63961
+ warnings: []
63962
+ };
63963
+ let runHeld = false;
63964
+ const holdRunOpen = () => {
63965
+ if (!runHeld && decisions.length > 1) runHeld = deps.executionTracker?.holdRunForPendingJobs(runId) ?? false;
63966
+ };
63967
+ try {
63968
+ for (const decision of decisions) {
63969
+ const workflow = shared.fullLockFile.workflows.find((w) => w.name === decision.workflowName);
63970
+ if (!workflow) continue;
63971
+ const ctx = buildTestDispatchContext(shared, workflow, decision, runId);
63972
+ ctx.onRunRegistered = holdRunOpen;
63973
+ const result = await dispatchMatchedWorkflow(ctx);
63974
+ out.jobIds.push(...result.dispatchedJobIds);
63975
+ out.deferredJobCount += result.deferredJobCount ?? 0;
63976
+ if (result.envWarnings?.length) out.warnings.push(...result.envWarnings);
63977
+ }
63978
+ } finally {
63979
+ if (runHeld) await deps.executionTracker?.releasePendingJobsHold(runId);
63980
+ }
63981
+ return out;
63982
+ }
63884
63983
  /**
63885
63984
  * Process a test trigger through the shared dispatch core: one
63886
- * `WorkflowDispatchContext` per matched workflow, into `dispatchMatchedWorkflow`.
63985
+ * `WorkflowDispatchContext` per matched workflow, into `dispatchMatchedWorkflow`,
63986
+ * each under its own run id.
63887
63987
  */
63888
63988
  async function processTestTrigger(input, deps) {
63889
63989
  const runId = randomUUID();
@@ -63919,6 +64019,13 @@ async function processTestTrigger(input, deps) {
63919
64019
  jobIds: []
63920
64020
  };
63921
64021
  const matchedDecisions = matchResult;
64022
+ const plan = planTestRuns(input, fullLockFile, matchedDecisions, runId);
64023
+ if (!Array.isArray(plan)) return {
64024
+ runId,
64025
+ status: "rejected",
64026
+ reason: plan.rejected,
64027
+ jobIds: []
64028
+ };
63922
64029
  const resolvedOrgId = deps.db ? await resolveOrgId(deps.db, input.routingKey) : "__default__";
63923
64030
  const dispatchInputsResult = resolveDispatchInputs(input, fullLockFile, matchedDecisions);
63924
64031
  if ("rejected" in dispatchInputsResult) return {
@@ -63934,13 +64041,13 @@ async function processTestTrigger(input, deps) {
63934
64041
  reason: namespacedResult.rejected,
63935
64042
  jobIds: []
63936
64043
  };
63937
- await storeFixturePayload(input, deps, runId);
64044
+ for (const planned of plan) await storeFixturePayload(input, deps, planned.runId);
63938
64045
  const cliSecrets = decryptCliSecrets(input);
63939
64046
  const testDeps = deps.secretResolver ? {
63940
64047
  ...deps,
63941
64048
  secretResolver: new DecoratingSecretResolver(deps.secretResolver, cliSecrets)
63942
64049
  } : deps;
63943
- deps.executionTracker?.markTestRun(runId);
64050
+ for (const planned of plan) deps.executionTracker?.markTestRun(planned.runId);
63944
64051
  const provider = inlineProvider ?? input.routingKey.split(":")[0] ?? "local";
63945
64052
  const shared = {
63946
64053
  input,
@@ -63966,34 +64073,31 @@ async function processTestTrigger(input, deps) {
63966
64073
  const jobIds = [];
63967
64074
  const warnings = [];
63968
64075
  let deferredJobCount = 0;
63969
- let runHeld = false;
63970
- const holdRunOpen = () => {
63971
- if (!runHeld && matchedDecisions.length > 1) runHeld = deps.executionTracker?.holdRunForPendingJobs(runId) ?? false;
63972
- };
63973
- try {
63974
- for (const decision of matchedDecisions) {
63975
- const workflow = fullLockFile.workflows.find((w) => w.name === decision.workflowName);
63976
- if (!workflow) continue;
63977
- const ctx = buildTestDispatchContext(shared, workflow, decision, runId);
63978
- ctx.onRunRegistered = holdRunOpen;
63979
- const result = await dispatchMatchedWorkflow(ctx);
63980
- jobIds.push(...result.dispatchedJobIds);
63981
- deferredJobCount += result.deferredJobCount ?? 0;
63982
- if (result.envWarnings?.length) warnings.push(...result.envWarnings);
63983
- }
63984
- } finally {
63985
- if (runHeld) await deps.executionTracker?.releasePendingJobsHold(runId);
64076
+ const runs = [];
64077
+ for (const planned of plan) {
64078
+ const dispatched = await dispatchPlannedRun(shared, deps, planned);
64079
+ jobIds.push(...dispatched.jobIds);
64080
+ deferredJobCount += dispatched.deferredJobCount;
64081
+ warnings.push(...dispatched.warnings);
64082
+ if (planned.decisions.length === 1) runs.push({
64083
+ runId: planned.runId,
64084
+ workflowName: planned.decisions[0].workflowName,
64085
+ jobIds: dispatched.jobIds
64086
+ });
63986
64087
  }
64088
+ const runsField = runs.length === plan.length ? { runs } : {};
63987
64089
  if (jobIds.length === 0 && deferredJobCount === 0) return {
63988
64090
  runId,
63989
64091
  status: "rejected",
63990
64092
  reason: "No jobs dispatched (all matched workflows had no static jobs or dispatch was rejected)",
63991
- jobIds: []
64093
+ jobIds: [],
64094
+ ...runsField
63992
64095
  };
63993
64096
  return {
63994
64097
  runId,
63995
64098
  status: "accepted",
63996
64099
  jobIds,
64100
+ ...runsField,
63997
64101
  ...warnings.length > 0 && { warnings }
63998
64102
  };
63999
64103
  }
@@ -64075,17 +64179,19 @@ async function handleTestTrigger(msg, deps) {
64075
64179
  checkMode: msg.checkMode,
64076
64180
  target: msg.target,
64077
64181
  dispatchInputs: msg.dispatchInputs,
64182
+ ...msg.acceptsMultipleRuns && { acceptsMultipleRuns: true },
64078
64183
  requestId: msg.requestId,
64079
64184
  actor: msg.actor
64080
64185
  }, deps);
64081
- runDetached(logger$59, "Access log write", () => deps.accessLog?.record({
64186
+ const runIds = result.runs?.length ? result.runs.map((r) => r.runId) : [result.runId];
64187
+ for (const runId of runIds) runDetached(logger$59, "Access log write", () => deps.accessLog?.record({
64082
64188
  orgId: deps.orgId ?? null,
64083
64189
  routingKey: deps.routingKey ?? msg.routingKey,
64084
64190
  actor: msg.actor,
64085
64191
  action: "run.trigger",
64086
64192
  target: {
64087
64193
  type: "run",
64088
- id: result.runId
64194
+ id: runId
64089
64195
  },
64090
64196
  requestId: msg.requestId,
64091
64197
  source: deps.accessLogSource ?? AccessLogSource.enum.platform_proxy,
@@ -64093,10 +64199,11 @@ async function handleTestTrigger(msg, deps) {
64093
64199
  ...result.reason ? { errorMessage: result.reason } : {}
64094
64200
  }), {
64095
64201
  requestId: msg.requestId,
64096
- runId: result.runId
64202
+ runId
64097
64203
  });
64098
64204
  return {
64099
64205
  runId: result.runId,
64206
+ ...result.runs && { runs: result.runs },
64100
64207
  status: result.status,
64101
64208
  ...result.reason ? { reason: result.reason } : {},
64102
64209
  jobIds: result.jobIds,
@@ -64131,7 +64238,8 @@ async function handleTestRunStatus(msg, deps) {
64131
64238
  const run = await deps.db.selectFrom("execution_runs").select([
64132
64239
  "run_id",
64133
64240
  "status",
64134
- "is_test_run"
64241
+ "is_test_run",
64242
+ "failure_reason"
64135
64243
  ]).where("run_id", "=", msg.runId).executeTakeFirst();
64136
64244
  if (!run || !run.is_test_run) return { error: "Run not found" };
64137
64245
  const jobs = await deps.db.selectFrom("execution_jobs").select([
@@ -64151,6 +64259,7 @@ async function handleTestRunStatus(msg, deps) {
64151
64259
  errorMessage: j.error_message ?? null,
64152
64260
  durationMs: j.duration_ms ?? null
64153
64261
  })),
64262
+ failureReason: run.failure_reason ?? null,
64154
64263
  done: TERMINAL_RUN_STATES.has(run.status)
64155
64264
  };
64156
64265
  }
@@ -64227,14 +64336,18 @@ async function collectRunLogLines(runId, logStorage) {
64227
64336
  async function readFromFileOffsets(runId, logStorage, offsets, terminal) {
64228
64337
  const next = { ...offsets };
64229
64338
  const lines = [];
64339
+ const lineJobNames = [];
64230
64340
  if (logStorage) for (const file of await listRunLogFiles(runId, logStorage)) {
64231
64341
  const fileLines = splitLogLines((await logStorage.read(file.path)).data, terminal);
64232
64342
  const from = Math.min(offsets[file.key] ?? 0, fileLines.length);
64233
- lines.push(...fileLines.slice(from));
64343
+ const fresh = fileLines.slice(from);
64344
+ lines.push(...fresh);
64345
+ for (let i = 0; i < fresh.length; i++) lineJobNames.push(file.jobName);
64234
64346
  next[file.key] = Math.max(offsets[file.key] ?? 0, fileLines.length);
64235
64347
  }
64236
64348
  return {
64237
64349
  lines,
64350
+ lineJobNames,
64238
64351
  nextCursor: Object.values(next).reduce((sum, n) => sum + n, 0),
64239
64352
  done: terminal,
64240
64353
  nextLogCursor: encodeRunLogCursor(next)
@@ -66694,26 +66807,29 @@ var init_admin_registrations = __esmMin((() => {
66694
66807
  *
66695
66808
  * Sorting `execution_steps` by `step_index` alone puts a pseudo-step where its index landed,
66696
66809
  * not where it ran. That suits the cache pseudo-steps (indices above the real steps, mostly
66697
- * bracketing the work), but a job's image build runs strictly FIRST.
66810
+ * bracketing the work), but a job's image build and its init commands run strictly FIRST.
66698
66811
  *
66699
66812
  * The index cannot express this: `step.status` rejects a negative index (an agent sending one
66700
66813
  * to an older orchestrator is disconnected mid-job), and the check-run reporter
66701
66814
  * (`steps[stepIndex]`) and the dashboard (log fetching gated on `stepIndex >= 0`) treat a
66702
- * negative index as absent. So the ordering is expressed by TYPE, here, once.
66815
+ * negative index as absent. So the ordering is expressed by TYPE, as the engine's
66816
+ * `StepPhase`, here, once.
66703
66817
  */
66704
66818
  /**
66705
- * Sort key placing setup pseudo-steps (run before the job's real steps) first. Apply BEFORE
66706
- * the `step_index` sort:
66819
+ * Sort key placing steps by `StepPhase`: setup pseudo-steps, then init commands, then the
66820
+ * job's steps. Apply BEFORE the `step_index` sort:
66707
66821
  *
66708
66822
  * ```ts
66709
66823
  * qb.orderBy(setupStepsFirst()).orderBy('step_index', 'asc')
66710
66824
  * ```
66711
66825
  *
66712
66826
  * Untyped steps and every real step share one key, so `step_index` alone orders each group.
66827
+ * The init pattern matches `initStepIndex`: the prefix followed by digits only.
66713
66828
  */
66714
66829
  function setupStepsFirst() {
66715
66830
  const setupTypes = SetupStepType.options;
66716
- return sql`case when step_type in (${sql.join(setupTypes.map((t) => sql.lit(t)))}) then 0 else 1 end`;
66831
+ const initPattern = `^${INIT_STEP_TYPE_PREFIX}[0-9]+$`;
66832
+ return sql`case when step_type in (${sql.join(setupTypes.map((t) => sql.lit(t)))}) then ${sql.lit(StepPhase.options.indexOf(StepPhase.enum.setup))} when step_type ~ ${sql.lit(initPattern)} then ${sql.lit(StepPhase.options.indexOf(StepPhase.enum.init))} else ${sql.lit(StepPhase.options.indexOf(StepPhase.enum.steps))} end`;
66717
66833
  }
66718
66834
  var init_step_display_order = __esmMin((() => {}));
66719
66835
  //#endregion
@@ -67216,6 +67332,19 @@ function parseCountFlag(raw) {
67216
67332
  function spansWorkflow(workflowName) {
67217
67333
  return (eb) => eb.or([eb("execution_runs.workflow_name", "=", workflowName), eb.exists(eb.selectFrom("execution_jobs").select("execution_jobs.job_id").whereRef("execution_jobs.run_id", "=", "execution_runs.run_id").where("execution_jobs.workflow_name", "=", workflowName))]);
67218
67334
  }
67335
+ /**
67336
+ * `?runIdPrefix=`: the run ids that start with the given characters, as a UUID range
67337
+ * the primary-key index answers. A full run id is its own one-id range.
67338
+ */
67339
+ function parseRunIdPrefixQuery(raw) {
67340
+ if (raw === void 0 || raw === "") return void 0;
67341
+ const ref = parseRunIdRef(raw);
67342
+ if (ref.kind === "invalid") return { error: `Invalid "runIdPrefix" '${raw}': ${ref.reason}` };
67343
+ return ref.kind === "uuid" ? {
67344
+ low: ref.runId,
67345
+ high: ref.runId
67346
+ } : runIdPrefixRange(ref.hex);
67347
+ }
67219
67348
  /** Run routes, mounted at /api/v1/admin/runs. */
67220
67349
  function createAdminRunRoutes(deps) {
67221
67350
  const app = createAdminApp(logger$51);
@@ -67225,12 +67354,16 @@ function createAdminRunRoutes(deps) {
67225
67354
  });
67226
67355
  app.use("/api/v1/admin/runs", authMiddleware);
67227
67356
  app.use("/api/v1/admin/runs/*", authMiddleware);
67357
+ app.use("/api/v1/admin/runs/:runId", requireFullRunId);
67358
+ app.use("/api/v1/admin/runs/:runId/*", requireFullRunId);
67228
67359
  app.get("/api/v1/admin/runs", async (c) => {
67229
67360
  deps.rbac.requirePermission(c.get("role"), "run.read");
67230
67361
  const statuses = parseStatus(c.req.query("status"));
67231
67362
  const workflowName = c.req.query("workflowName");
67232
67363
  const repo = c.req.query("repo");
67233
67364
  const deliveryId = c.req.query("deliveryId");
67365
+ const runIdPrefix = parseRunIdPrefixQuery(c.req.query("runIdPrefix"));
67366
+ if (runIdPrefix && "error" in runIdPrefix) return c.json({ error: runIdPrefix.error }, 400);
67234
67367
  const sinceParsed = parseSince(c.req.query("since"));
67235
67368
  if (!sinceParsed.ok) return c.json({ error: "Invalid \"since\" query parameter: expected ISO-8601 timestamp" }, 400);
67236
67369
  const since = sinceParsed.value;
@@ -67247,6 +67380,7 @@ function createAdminRunRoutes(deps) {
67247
67380
  if (workflowName) countQuery = countQuery.where(spansWorkflow(workflowName));
67248
67381
  if (repo) countQuery = countQuery.where("repo_identifier", "=", repo);
67249
67382
  if (deliveryId) countQuery = countQuery.where("delivery_id", "=", deliveryId);
67383
+ if (runIdPrefix) countQuery = countQuery.where("run_id", ">=", runIdPrefix.low).where("run_id", "<=", runIdPrefix.high);
67250
67384
  if (since) countQuery = countQuery.where("created_at", ">", since);
67251
67385
  if (tokenRoutingKey) countQuery = countQuery.where("routing_key", "=", tokenRoutingKey);
67252
67386
  if (countOnly) {
@@ -67282,6 +67416,7 @@ function createAdminRunRoutes(deps) {
67282
67416
  if (workflowName) query = query.where(spansWorkflow(workflowName));
67283
67417
  if (repo) query = query.where("repo_identifier", "=", repo);
67284
67418
  if (deliveryId) query = query.where("delivery_id", "=", deliveryId);
67419
+ if (runIdPrefix) query = query.where("run_id", ">=", runIdPrefix.low).where("run_id", "<=", runIdPrefix.high);
67285
67420
  if (since) query = query.where("created_at", ">", since);
67286
67421
  if (tokenRoutingKey) query = query.where("routing_key", "=", tokenRoutingKey);
67287
67422
  const [runs, countResult] = await Promise.all([query.orderBy("created_at", "desc").limit(limit).offset(offset).execute(), countQuery.executeTakeFirstOrThrow()]);
@@ -67313,7 +67448,7 @@ function createAdminRunRoutes(deps) {
67313
67448
  });
67314
67449
  app.get("/api/v1/admin/runs/:runId", async (c) => {
67315
67450
  deps.rbac.requirePermission(c.get("role"), "run.read");
67316
- const runId = c.req.param("runId");
67451
+ const runId = c.req.param("runId").toLowerCase();
67317
67452
  const run = await deps.db.selectFrom("execution_runs").select([
67318
67453
  "run_id",
67319
67454
  "workflow_name",
@@ -67377,7 +67512,7 @@ function createAdminRunRoutes(deps) {
67377
67512
  });
67378
67513
  app.get("/api/v1/admin/runs/:runId/jobs", async (c) => {
67379
67514
  deps.rbac.requirePermission(c.get("role"), "run.read");
67380
- const runId = c.req.param("runId");
67515
+ const runId = c.req.param("runId").toLowerCase();
67381
67516
  const includeSteps = parseCountFlag(c.req.query("includeSteps"));
67382
67517
  const runExists = await deps.db.selectFrom("execution_runs").select([
67383
67518
  "run_id",
@@ -67429,6 +67564,7 @@ function createAdminRunRoutes(deps) {
67429
67564
  jobSteps = [];
67430
67565
  stepsByJob.set(step.job_id, jobSteps);
67431
67566
  }
67567
+ const initIndex = initStepIndex(step.step_type);
67432
67568
  jobSteps.push({
67433
67569
  stepIndex: step.step_index,
67434
67570
  stepName: step.step_name,
@@ -67438,7 +67574,9 @@ function createAdminRunRoutes(deps) {
67438
67574
  durationMs: step.duration_ms,
67439
67575
  exitCode: step.exit_code,
67440
67576
  errorMessage: step.error_message,
67441
- stepType: step.step_type
67577
+ stepType: step.step_type,
67578
+ phase: stepPhase(step.step_type),
67579
+ ...initIndex !== void 0 && { initIndex }
67442
67580
  });
67443
67581
  }
67444
67582
  }
@@ -67465,7 +67603,7 @@ function createAdminRunRoutes(deps) {
67465
67603
  });
67466
67604
  app.get("/api/v1/admin/runs/:runId/structured", async (c) => {
67467
67605
  deps.rbac.requirePermission(c.get("role"), "run.read");
67468
- const runId = c.req.param("runId");
67606
+ const runId = c.req.param("runId").toLowerCase();
67469
67607
  const detail = await aggregateRunDetail(deps.db, runId);
67470
67608
  if (!detail) return c.json({ error: `Run ${runId} not found` }, 404);
67471
67609
  const denied = enforceRoutingKeyScope(c, detail.routingKey);
@@ -67474,7 +67612,7 @@ function createAdminRunRoutes(deps) {
67474
67612
  });
67475
67613
  app.get("/api/v1/admin/runs/:runId/jobs/:jobId/steps/:stepIndex/logs", async (c) => {
67476
67614
  deps.rbac.requirePermission(c.get("role"), "run.read");
67477
- const runId = c.req.param("runId");
67615
+ const runId = c.req.param("runId").toLowerCase();
67478
67616
  const jobId = c.req.param("jobId");
67479
67617
  const stepIndex = parseInt(c.req.param("stepIndex"), 10);
67480
67618
  if (Number.isNaN(stepIndex)) return c.json({ error: "Invalid stepIndex" }, 400);
@@ -67500,7 +67638,7 @@ function createAdminRunRoutes(deps) {
67500
67638
  });
67501
67639
  app.get("/api/v1/admin/runs/:runId/ephemeral-key", async (c) => {
67502
67640
  deps.rbac.requirePermission(c.get("role"), "run.read");
67503
- const runId = c.req.param("runId");
67641
+ const runId = c.req.param("runId").toLowerCase();
67504
67642
  const runExists = await deps.db.selectFrom("execution_runs").select(["run_id", "routing_key"]).where("run_id", "=", runId).executeTakeFirst();
67505
67643
  if (!runExists) return c.json({ error: `Run ${runId} not found` }, 404);
67506
67644
  const denied = enforceRoutingKeyScope(c, runExists.routing_key);
@@ -67518,7 +67656,7 @@ function createAdminRunRoutes(deps) {
67518
67656
  app.get("/api/v1/admin/runs/:runId/secret-outputs", async (c) => {
67519
67657
  const role = c.get("role");
67520
67658
  deps.rbac.requirePermission(role, "run.read");
67521
- const runId = c.req.param("runId");
67659
+ const runId = c.req.param("runId").toLowerCase();
67522
67660
  const outputKeyFilter = c.req.query("outputKey");
67523
67661
  const reveal = parseCountFlag(c.req.query("reveal"));
67524
67662
  if (reveal) {
@@ -67589,7 +67727,7 @@ function createAdminRunRoutes(deps) {
67589
67727
  });
67590
67728
  return app;
67591
67729
  }
67592
- var logger$51, RUN_STATUSES;
67730
+ var logger$51, RUN_STATUSES, requireFullRunId;
67593
67731
  var init_admin_runs = __esmMin((() => {
67594
67732
  init_step_display_order();
67595
67733
  init_secret_output_crypto();
@@ -67603,6 +67741,11 @@ var init_admin_runs = __esmMin((() => {
67603
67741
  init_admin_env();
67604
67742
  logger$51 = createLogger({ prefix: "admin-runs" });
67605
67743
  RUN_STATUSES = new Set(CANONICAL_STATUSES);
67744
+ requireFullRunId = async (c, next) => {
67745
+ const runId = c.req.param("runId") ?? "";
67746
+ if (!isRunId(runId)) return c.json({ error: `Invalid run id '${runId}': a run id is a full UUID. List runs (or filter with ?runIdPrefix=) to find it.` }, 400);
67747
+ return next();
67748
+ };
67606
67749
  }));
67607
67750
  //#endregion
67608
67751
  //#region src/cold-store/load-event-log-range.ts
@@ -68700,14 +68843,14 @@ function createHealthRoutes$1(deps = {}) {
68700
68843
  return createHealthRoutes({
68701
68844
  livenessInfo: () => {
68702
68845
  return {
68703
- version: "0.20.1",
68704
- buildDate: "2026-10-17T14:01:06.224Z",
68705
- sdkVersion: "0.20.1",
68846
+ version: "0.21.0",
68847
+ buildDate: "2026-10-18T02:27:04.078Z",
68848
+ sdkVersion: "0.21.0",
68706
68849
  sdkBundleHash: "b012bd8bace3df9e574252bf290c652bc39766da617acf51729a08586756266d",
68707
- sharedVersion: "0.20.1",
68850
+ sharedVersion: "0.21.0",
68708
68851
  sharedBundleHash: "23378ace59973f47203be3ff171bb7d5e66702eaa335927b390f94b9771a1f8b",
68709
- engineVersion: "0.20.1",
68710
- engineBundleHash: "406208ee50766cd0d4ce3ab1fd5dec4501d5117a74acd294b25736d9bd6e89bd"
68852
+ engineVersion: "0.21.0",
68853
+ engineBundleHash: "d9dfe7c806a7fdbd64da7ef48455a139c1d8db8510964ab79e233efdc3f171ba"
68711
68854
  };
68712
68855
  },
68713
68856
  readinessCheck: deps.db ? async () => {
@@ -68739,7 +68882,7 @@ function createCapabilitiesRoutes() {
68739
68882
  const app = new Hono();
68740
68883
  app.get("/api/v1/capabilities", (c) => {
68741
68884
  const manifest = {
68742
- orchestratorVersion: "0.20.1",
68885
+ orchestratorVersion: "0.21.0",
68743
68886
  protocolVersion: PROTOCOL_VERSION,
68744
68887
  minProtocolVersion: MIN_PROTOCOL_VERSION
68745
68888
  };
@@ -71243,6 +71386,7 @@ function createApp(deps) {
71243
71386
  jobName: deps.executionTracker.getJobName(msg.runId, msg.jobId) ?? msg.jobId,
71244
71387
  stepIndex: msg.stepIndex,
71245
71388
  stepName: msg.stepName,
71389
+ stepType: stepTypeOf(msg.data),
71246
71390
  state: msg.state,
71247
71391
  durationMs: msg.data?.durationMs ?? void 0,
71248
71392
  installationId: execContext.installationId,
@@ -71465,7 +71609,7 @@ function createApp(deps) {
71465
71609
  app.use("*", (c, next) => c.req.path.startsWith("/api/v1/cache/blob/") ? next() : webhookBodyLimit(c, next));
71466
71610
  if (deps.fsCache) registerBlobRoutes(app, deps.fsCache);
71467
71611
  app.post("/api/v1/admin/runs/:runId/cancel", async (c) => {
71468
- const runId = c.req.param("runId");
71612
+ const runId = c.req.param("runId").toLowerCase();
71469
71613
  if (!runId) return c.json({ error: "Missing runId" }, 400);
71470
71614
  if (!deps.adminDeps) return c.json({ error: "Admin API not available" }, 503);
71471
71615
  const auth = await resolveBearerAuth(c, {
@@ -71500,6 +71644,11 @@ function createApp(deps) {
71500
71644
  recordAccess("denied", `role "${tokenInfo.role}" lacks run.cancel`);
71501
71645
  return c.json({ error: "Permission denied: run.cancel required" }, 403);
71502
71646
  }
71647
+ if (!isRunId(runId)) {
71648
+ const error = `Invalid run id '${runId}': a run id is a full UUID.`;
71649
+ recordAccess("allowed", error);
71650
+ return c.json({ error }, 400);
71651
+ }
71503
71652
  try {
71504
71653
  let force = false;
71505
71654
  try {
@@ -71973,7 +72122,7 @@ var init_app = __esmMin((() => {
71973
72122
  init_agent_metrics_aggregator();
71974
72123
  init_run_detached();
71975
72124
  logger$38 = createLogger({ prefix: "app" });
71976
- ORCHESTRATOR_VERSION$2 = "0.20.1";
72125
+ ORCHESTRATOR_VERSION$2 = "0.21.0";
71977
72126
  SourceLocationStore = class {
71978
72127
  cache = /* @__PURE__ */ new Map();
71979
72128
  key(workflowName, jobName) {
@@ -91588,13 +91737,13 @@ var init_worker_core = __esmMin((() => {
91588
91737
  init_app_on_error();
91589
91738
  init_run_detached();
91590
91739
  init_peer_job_cancel();
91591
- ORCHESTRATOR_VERSION$1 = "0.20.1";
91592
- WORKER_SDK_VERSION = "0.20.1";
91740
+ ORCHESTRATOR_VERSION$1 = "0.21.0";
91741
+ WORKER_SDK_VERSION = "0.21.0";
91593
91742
  WORKER_SDK_BUNDLE_HASH = "b012bd8bace3df9e574252bf290c652bc39766da617acf51729a08586756266d";
91594
- WORKER_SHARED_VERSION = "0.20.1";
91743
+ WORKER_SHARED_VERSION = "0.21.0";
91595
91744
  WORKER_SHARED_BUNDLE_HASH = "23378ace59973f47203be3ff171bb7d5e66702eaa335927b390f94b9771a1f8b";
91596
- WORKER_ENGINE_VERSION = "0.20.1";
91597
- WORKER_ENGINE_BUNDLE_HASH = "406208ee50766cd0d4ce3ab1fd5dec4501d5117a74acd294b25736d9bd6e89bd";
91745
+ WORKER_ENGINE_VERSION = "0.21.0";
91746
+ WORKER_ENGINE_BUNDLE_HASH = "d9dfe7c806a7fdbd64da7ef48455a139c1d8db8510964ab79e233efdc3f171ba";
91598
91747
  logger$2 = createLogger({ prefix: "worker" });
91599
91748
  DRAIN_TIMEOUT_MS = 3e5;
91600
91749
  }));
@@ -91623,13 +91772,13 @@ init_webhook_url_resolvers();
91623
91772
  init_run_detached();
91624
91773
  init_peer_job_cancel();
91625
91774
  init_orphan_requests();
91626
- const ORCHESTRATOR_VERSION = "0.20.1";
91627
- const SDK_VERSION = "0.20.1";
91775
+ const ORCHESTRATOR_VERSION = "0.21.0";
91776
+ const SDK_VERSION = "0.21.0";
91628
91777
  const SDK_BUNDLE_HASH = "b012bd8bace3df9e574252bf290c652bc39766da617acf51729a08586756266d";
91629
- const SHARED_VERSION = "0.20.1";
91778
+ const SHARED_VERSION = "0.21.0";
91630
91779
  const SHARED_BUNDLE_HASH = "23378ace59973f47203be3ff171bb7d5e66702eaa335927b390f94b9771a1f8b";
91631
- const ENGINE_VERSION = "0.20.1";
91632
- const ENGINE_BUNDLE_HASH = "406208ee50766cd0d4ce3ab1fd5dec4501d5117a74acd294b25736d9bd6e89bd";
91780
+ const ENGINE_VERSION = "0.21.0";
91781
+ const ENGINE_BUNDLE_HASH = "d9dfe7c806a7fdbd64da7ef48455a139c1d8db8510964ab79e233efdc3f171ba";
91633
91782
  const otelSdk = initTelemetry({
91634
91783
  serviceName: "kici-orchestrator",
91635
91784
  otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT