@kici-dev/engine 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/audit/access-log-policy.js +1 -0
  2. package/dist/audit/retention-policy.js +2 -0
  3. package/dist/context/held-run-job-id.d.ts +26 -10
  4. package/dist/context/held-run-job-id.js +30 -11
  5. package/dist/context/index.d.ts +1 -1
  6. package/dist/context/index.js +3 -3
  7. package/dist/context/types.d.ts +10 -1
  8. package/dist/context/types.js +10 -1
  9. package/dist/index.d.ts +4 -2
  10. package/dist/index.js +14 -11
  11. package/dist/labels.d.ts +66 -12
  12. package/dist/labels.js +74 -17
  13. package/dist/mcp/held-run-resolve.d.ts +40 -0
  14. package/dist/mcp/held-run-resolve.js +68 -15
  15. package/dist/mcp/tool-schemas.d.ts +4 -0
  16. package/dist/mcp/tool-schemas.js +13 -1
  17. package/dist/metrics/catalog-policy.d.ts +6 -3
  18. package/dist/metrics/catalog-policy.js +31 -10
  19. package/dist/metrics/metric-catalog.generated.d.ts +110 -0
  20. package/dist/metrics/metric-catalog.generated.js +132 -0
  21. package/dist/protocol/dashboard-global-workflows.js +2 -2
  22. package/dist/protocol/event-log-payload.js +1 -1
  23. package/dist/protocol/messages/access-log.d.ts +5 -0
  24. package/dist/protocol/messages/access-log.js +1 -0
  25. package/dist/protocol/messages/actor.d.ts +13 -2
  26. package/dist/protocol/messages/actor.js +16 -5
  27. package/dist/protocol/messages/common.js +1 -1
  28. package/dist/protocol/messages/dashboard-global-workflows.d.ts +21 -0
  29. package/dist/protocol/messages/dashboard-global-workflows.js +28 -1
  30. package/dist/protocol/messages/dashboard.d.ts +43 -5
  31. package/dist/protocol/messages/dashboard.js +55 -6
  32. package/dist/protocol/messages/execution-status.d.ts +41 -0
  33. package/dist/protocol/messages/execution-status.js +53 -2
  34. package/dist/protocol/messages/git-credential-relay.d.ts +78 -0
  35. package/dist/protocol/messages/git-credential-relay.js +86 -0
  36. package/dist/protocol/messages/orchestrator-agent.d.ts +89 -0
  37. package/dist/protocol/messages/orchestrator-agent.js +98 -3
  38. package/dist/protocol/messages/peer.d.ts +7 -0
  39. package/dist/protocol/messages/peer.js +18 -1
  40. package/dist/protocol/messages/platform-orchestrator.d.ts +150 -0
  41. package/dist/protocol/messages/platform-orchestrator.js +164 -19
  42. package/dist/protocol/version.d.ts +19 -2
  43. package/dist/protocol/version.js +20 -3
  44. package/dist/provenance/verify.js +11 -10
  45. package/dist/provider/check-status-poster.d.ts +24 -3
  46. package/dist/provider/contributor-resolver.d.ts +11 -3
  47. package/dist/provider/git-credential.d.ts +77 -0
  48. package/dist/provider/git-credential.js +10 -0
  49. package/dist/provider/index.d.ts +2 -0
  50. package/dist/provider/index.js +2 -1
  51. package/dist/provider/webhook-normalizer.d.ts +12 -12
  52. package/dist/repo/pattern-negation.d.ts +73 -0
  53. package/dist/repo/pattern-negation.js +86 -0
  54. package/dist/scaler/registry-auth.d.ts +18 -0
  55. package/dist/scaler/registry-auth.js +28 -0
  56. package/dist/scaler/scaler-backend-type.d.ts +35 -0
  57. package/dist/scaler/scaler-backend-type.js +39 -2
  58. package/dist/scaler/scaler-events.d.ts +79 -0
  59. package/dist/scaler/scaler-events.js +87 -0
  60. package/dist/trigger/content-requirements.js +1 -1
  61. package/dist/trigger/decision-trace.d.ts +79 -0
  62. package/dist/trigger/decision-trace.js +116 -8
  63. package/dist/trigger/matcher.js +4 -2
  64. package/dist/trigger/types.d.ts +87 -7
  65. package/dist/trigger/types.js +6 -1
  66. package/dist/ws/rate-limiter.js +3 -3
  67. package/package.json +11 -3
  68. package/sbom.spdx.json +15 -15
package/dist/labels.js CHANGED
@@ -253,6 +253,39 @@ function capabilityLabel(name) {
253
253
  */
254
254
  const SSH_TRANSPORT_CAPABILITY = "kici:capability:ssh-transport";
255
255
  /**
256
+ * Runtime-fact label prefix — `kici:runtime:<name>` states something the AGENT
257
+ * discovered about its own host.
258
+ *
259
+ * Deliberately NOT `kici:capability:`. That prefix grants a privilege
260
+ * (`ssh-transport` custodies the bootstrap SSH key), so it must stay
261
+ * token-bound and an agent must never be able to self-assert it. A runtime fact
262
+ * grants nothing: it reports what the host can do, the way `kici:os:` reports
263
+ * what the host is, and it is self-reported for the same reason — the scaler
264
+ * cannot predict it at token-mint time.
265
+ */
266
+ const RUNTIME_LABEL_PREFIX = "kici:runtime:";
267
+ /** Build a `kici:runtime:<name>` label. */
268
+ function runtimeLabel(name) {
269
+ return `${RUNTIME_LABEL_PREFIX}${name}`;
270
+ }
271
+ /**
272
+ * Runtime facts an agent reports about its own host at registration.
273
+ *
274
+ * - `docker` / `podman` — that container runtime's socket answered, so the
275
+ * agent can nest a job container.
276
+ * - `container-build` — a `docker` / `podman` CLI is on PATH, so the agent can
277
+ * build a job's image from a Dockerfile. Distinct from the two above: a host
278
+ * reachable only through a mounted socket can RUN containers but not BUILD
279
+ * one, because the build shells out to the CLI.
280
+ */
281
+ const RuntimeFact = z.enum([
282
+ "docker",
283
+ "podman",
284
+ "container-build"
285
+ ]);
286
+ /** The label a job's `runsOn` names to require a host that can build an image. */
287
+ const CONTAINER_BUILD_RUNTIME_LABEL = runtimeLabel(RuntimeFact.enum["container-build"]);
288
+ /**
256
289
  * The `kici:init` lifecycle label carried by a temporary init-runner agent
257
290
  * brought up on a fresh box for bootstrap. Marks it as ephemeral + privileged;
258
291
  * it dies on reboot and is reaped. Distinct from `kici:role:init-runner`
@@ -297,32 +330,56 @@ function resolveRoleLabels(roles) {
297
330
  }
298
331
  /**
299
332
  * The full label set a scaler injects into an agent it spawns: the base label
300
- * set plus the scaler-assigned `kici:agent:`, `kici:scaler:`, and `kici:role:`
301
- * labels. This is exactly what the scaler writes into `KICI_LABELS`, and it is
302
- * the set an ephemeral agent token must be bound to — the agent then adds only
303
- * the self-reported platform facts (`kici:os:`, `kici:arch:`, `kici:host:`) at
304
- * registration, which the orchestrator's register-time scope gate exempts (see
305
- * `isSelfReportedLabel`). Keep this aligned with the agent's wire-label
306
- * construction in `orchestrator-client.ts#sendAgentRegister`.
307
- */
308
- function scalerAgentLabels(labelSet, backendType, backendName, roles) {
309
- return [
333
+ * set plus the scaler-assigned `kici:agent:`, `kici:scaler:`, `kici:role:`, and
334
+ * platform-taint labels. This is exactly what the scaler writes into
335
+ * `KICI_LABELS`, and it is the set an ephemeral agent token must be bound to —
336
+ * the agent then adds only the self-reported platform facts (`kici:os:`,
337
+ * `kici:arch:`, `kici:host:`) at registration, which the orchestrator's
338
+ * register-time scope gate exempts (see `isSelfReportedLabel`). Keep this
339
+ * aligned with the agent's wire-label construction in
340
+ * `orchestrator-client.ts#sendAgentRegister`.
341
+ *
342
+ * `platformTaints` are the plain tokens (`windows`, `macos`, `arm64`) derived
343
+ * from the pool's resolved platform. A taint is a routing GRANT, not a host
344
+ * fact: it is what a job's `runsOn` names to reach this pool, and the matcher
345
+ * gates on it. So the orchestrator asserts it here — bound into the agent's
346
+ * ephemeral token — rather than letting the agent self-report it. Adding these
347
+ * prefixes to `SELF_REPORTED_LABEL_PREFIXES` instead would let any agent claim
348
+ * any platform unchallenged, which is the opposite of a gate. The caller must
349
+ * derive them from the same source the gate uses
350
+ * (`ScalerManager.resolveScalerPlatform`) — deriving them twice is exactly the
351
+ * defect this parameter closes.
352
+ *
353
+ * The result is de-duplicated in first-seen order: a warm pool measures its
354
+ * readiness with a label set that already carries the taints, so it passes them
355
+ * as `labelSet` AND as `platformTaints`, and a label repeated in the set an
356
+ * ephemeral token is bound to is never meaningful.
357
+ */
358
+ function scalerAgentLabels(labelSet, backendType, backendName, roles, platformTaints = []) {
359
+ return [.../* @__PURE__ */ new Set([
310
360
  ...labelSet,
311
361
  agentTypeLabel(backendType),
312
362
  scalerLabel(backendName),
313
- ...resolveRoleLabels(roles)
314
- ];
363
+ ...resolveRoleLabels(roles),
364
+ ...platformTaints
365
+ ])];
315
366
  }
316
367
  /**
317
368
  * Label-category prefixes the agent self-reports at registration from its own
318
- * host (operating system, CPU architecture, hostname). They are immutable
319
- * platform facts, not authorization grants, so the orchestrator's register-time
320
- * label-scope gate does not require an ephemeral token to be bound to them.
369
+ * host: operating system, CPU architecture, hostname, and runtime facts. They
370
+ * are platform facts, not authorization grants, so the orchestrator's
371
+ * register-time label-scope gate does not require an ephemeral token to be
372
+ * bound to them.
373
+ *
374
+ * Adding a prefix here lets any agent assert it unchallenged. That is correct
375
+ * for a fact about the host and WRONG for anything that grants a privilege —
376
+ * `kici:capability:` is absent from this list for exactly that reason.
321
377
  */
322
378
  const SELF_REPORTED_LABEL_PREFIXES = [
323
379
  "kici:os:",
324
380
  "kici:arch:",
325
- "kici:host:"
381
+ "kici:host:",
382
+ RUNTIME_LABEL_PREFIX
326
383
  ];
327
384
  /**
328
385
  * True if a label is a self-reported platform fact (os/arch/host) rather than a
@@ -360,6 +417,6 @@ function separateLabels(labels) {
360
417
  };
361
418
  }
362
419
  //#endregion
363
- export { CAPABILITY_LABEL_PREFIX, HOST_LABEL_PREFIX, INIT_LABEL, INIT_RUNNER_ROLE_LABEL, KNOWN_ROLES, PLATFORM_TAINT_LABELS, PRIVILEGED_ROOT_LABEL, RESERVED_LABEL_PREFIX, ROLE_LABEL_PREFIX, SELF_REPORTED_LABEL_PREFIXES, SSH_TRANSPORT_CAPABILITY, ScalerArch, ScalerOs, agentTypeLabel, capabilityLabel, deriveOsArchLabels, derivePlatformTaints, hostLabel, hostToScalerPlatform, isAutoLabel, isSelfReportedLabel, mergeAutoLabels, nodeArchToScalerArch, nodePlatformToScalerOs, normalizeRunsOn, parseHostLabel, platformToOsArchLabels, platformToTaints, resolveRoleLabels, roleToLabel, scalerAgentLabels, scalerLabel, scalerPlatformSchema, separateLabels, validateNoReservedLabels };
420
+ export { CAPABILITY_LABEL_PREFIX, CONTAINER_BUILD_RUNTIME_LABEL, HOST_LABEL_PREFIX, INIT_LABEL, INIT_RUNNER_ROLE_LABEL, KNOWN_ROLES, PLATFORM_TAINT_LABELS, PRIVILEGED_ROOT_LABEL, RESERVED_LABEL_PREFIX, ROLE_LABEL_PREFIX, RUNTIME_LABEL_PREFIX, RuntimeFact, SELF_REPORTED_LABEL_PREFIXES, SSH_TRANSPORT_CAPABILITY, ScalerArch, ScalerOs, agentTypeLabel, capabilityLabel, deriveOsArchLabels, derivePlatformTaints, hostLabel, hostToScalerPlatform, isAutoLabel, isSelfReportedLabel, mergeAutoLabels, nodeArchToScalerArch, nodePlatformToScalerOs, normalizeRunsOn, parseHostLabel, platformToOsArchLabels, platformToTaints, resolveRoleLabels, roleToLabel, runtimeLabel, scalerAgentLabels, scalerLabel, scalerPlatformSchema, separateLabels, validateNoReservedLabels };
364
421
 
365
422
  //# sourceMappingURL=labels.js.map
@@ -15,6 +15,17 @@ export interface HeldRunSummary {
15
15
  * orchestrator's response.
16
16
  */
17
17
  holdType?: string;
18
+ /**
19
+ * Bound context that requested the hold, when the row carries one. Null once
20
+ * the context is deleted, and absent from an older orchestrator's response.
21
+ */
22
+ contextName?: string | null;
23
+ /** Persisted `held_runs.queue_type` (`context` / `security`), when present. */
24
+ queueType?: string;
25
+ /** Why the hold was requested, when the row carries a reason. */
26
+ reason?: string | null;
27
+ /** When the hold expires, as an ISO timestamp; null when it never does. */
28
+ expiresAt?: string | null;
18
29
  /** Computed drift payload for a `when: 'drift'` step hold; absent otherwise. */
19
30
  payload?: {
20
31
  summaryMarkdown: string;
@@ -27,6 +38,26 @@ export interface HeldRunFilter {
27
38
  job?: string;
28
39
  /** Match a step-scoped hold by its step index (compared as a string). */
29
40
  step?: string;
41
+ /**
42
+ * Match one hold by its own id, ignoring every other filter.
43
+ *
44
+ * The escape hatch for an ambiguity nothing else can resolve. A job can carry
45
+ * more than one pending hold — an SDK `requireApproval` and a security-typed
46
+ * context gate produce two job-scoped rows under one job name — and while
47
+ * {@link holdType} separates that pair, nothing guarantees a future pair
48
+ * differs in type. An id always does, and the candidate list prints the ids
49
+ * whenever two holds are otherwise indistinguishable.
50
+ */
51
+ holdId?: string;
52
+ /**
53
+ * Narrow to holds of one type (`reviewer` / `timer` / `concurrency` /
54
+ * `security`), normalized through `normalizePersistedHoldType` on both sides
55
+ * so a legacy `approval` row answers to `reviewer`.
56
+ *
57
+ * Composes with {@link job} / {@link step} rather than replacing them: a
58
+ * matrix whose children each carry two holds needs both halves.
59
+ */
60
+ holdType?: string;
30
61
  }
31
62
  /** Resolution result: either a held-run id or a user-facing error message. */
32
63
  export type ResolveResult = {
@@ -40,10 +71,19 @@ export type ResolveResult = {
40
71
  /**
41
72
  * Resolve the held-run id matching the filter from a list of pending holds.
42
73
  *
74
+ * - `holdId` names one hold outright and ignores every other filter.
43
75
  * - `step` requires `job` and matches a `step`-scoped hold whose step index
44
76
  * equals the given value.
45
77
  * - `job` alone matches a `job`/`workflow`-scoped hold for that job.
78
+ * - `holdType` narrows whichever set the above produced, and may be used on its
79
+ * own.
46
80
  * - With no filter, the sole pending hold is used; ambiguity is an error.
81
+ *
82
+ * One job can carry more than one pending hold: an SDK `requireApproval` paired
83
+ * with a security-typed context gate writes two job-scoped rows under one job
84
+ * name, because two independent requirements gate the job and both must be
85
+ * answered. `job` alone cannot separate those, which is what `holdType` and
86
+ * `holdId` are for.
47
87
  */
48
88
  export declare function resolveHeldRunId(holds: readonly HeldRunSummary[], filter: HeldRunFilter): ResolveResult;
49
89
  //# sourceMappingURL=held-run-resolve.d.ts.map
@@ -1,38 +1,86 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { HeldRunStatus } from "../context/held-run-status.js";
3
+ import { normalizePersistedHoldType } from "../context/hold-type.js";
3
4
  //#region src/mcp/held-run-resolve.ts
4
5
  /**
5
6
  * Shared held-run resolution for the `kici approve` / `kici reject` commands and
6
7
  * the developer MCP `approve_run` / `reject_run` tools.
7
8
  *
8
- * Both surfaces first list the pending holds for a run, then resolve the one
9
- * the caller named via `job` / `step` (or the sole pending hold when there is
10
- * exactly one and no filter is given). The resolution is a pure function so it
11
- * can be unit-tested without HTTP, and it imports nothing beyond the shared
12
- * held-run vocabulary, so it is safe to re-export from the browser-facing
13
- * engine barrel.
9
+ * Both surfaces first list the pending holds for a run, then resolve the one the
10
+ * caller named via `job` / `step` / `holdType` / `holdId` (or the sole pending
11
+ * hold when there is exactly one and no filter is given). The resolution is a
12
+ * pure function so it can be unit-tested without HTTP, and it imports nothing
13
+ * beyond the shared held-run vocabulary, so it is safe to re-export from the
14
+ * browser-facing engine barrel.
15
+ *
16
+ * A job name is not a unique key: two independent requirements can gate one job,
17
+ * and each writes its own pending row under that name. So every ambiguity path
18
+ * here has to end at a filter that can actually separate them, and the candidate
19
+ * list has to print enough for the caller to pick one.
14
20
  */
15
21
  /**
16
22
  * Name a hold the way the caller would type it: the literal `jobId` (so an
17
23
  * `__install__…` sentinel or an opaque legacy id is reproduced verbatim), with
18
- * the step index prefixed for a step-scoped hold.
24
+ * the step index prefixed for a step-scoped hold, and the hold type appended.
25
+ *
26
+ * The type is what makes the candidate list actionable when one job carries two
27
+ * holds — without it both rows rendered as the bare job name, so the list
28
+ * collapsed to a single entry and named a `--job` filter that could not tell
29
+ * them apart. It is omitted when the orchestrator's list response carried none
30
+ * (an older build), which renders exactly as it did before.
19
31
  */
20
32
  function describeHold(hold) {
21
33
  const job = hold.jobId || "(unnamed hold)";
22
- if (hold.holdScope === "step" && hold.stepIndex != null) return `step ${hold.stepIndex} of ${job}`;
23
- return job;
34
+ const base = hold.holdScope === "step" && hold.stepIndex != null ? `step ${hold.stepIndex} of ${job}` : job;
35
+ return hold.holdType ? `${base} (${normalizePersistedHoldType(hold.holdType)})` : base;
24
36
  }
25
- /** The distinct hold descriptions, in listing order, for an error message. */
37
+ /**
38
+ * The hold descriptions, in listing order, for an error message.
39
+ *
40
+ * A description that occurs once is listed once. One that occurs more than once
41
+ * is listed per hold WITH its id, because at that point the description alone
42
+ * cannot name which hold the caller means and `--hold <id>` is the only filter
43
+ * left that can. Silently de-duplicating those is what made the old message
44
+ * offer a disambiguator it had already discarded the information for.
45
+ */
26
46
  function listCandidates(holds) {
27
- return [...new Set(holds.map(describeHold))].join(", ");
47
+ const counts = /* @__PURE__ */ new Map();
48
+ for (const hold of holds) {
49
+ const text = describeHold(hold);
50
+ counts.set(text, (counts.get(text) ?? 0) + 1);
51
+ }
52
+ const emitted = /* @__PURE__ */ new Set();
53
+ const out = [];
54
+ for (const hold of holds) {
55
+ const text = describeHold(hold);
56
+ if ((counts.get(text) ?? 0) > 1) {
57
+ out.push(`${text} [${hold.id}]`);
58
+ continue;
59
+ }
60
+ if (emitted.has(text)) continue;
61
+ emitted.add(text);
62
+ out.push(text);
63
+ }
64
+ return out.join(", ");
28
65
  }
66
+ /** The disambiguators a caller can reach for, named the way the CLI spells them. */
67
+ const DISAMBIGUATORS = "--job <name>, --step <index>, --hold-type <type> or --hold <id>";
29
68
  /**
30
69
  * Resolve the held-run id matching the filter from a list of pending holds.
31
70
  *
71
+ * - `holdId` names one hold outright and ignores every other filter.
32
72
  * - `step` requires `job` and matches a `step`-scoped hold whose step index
33
73
  * equals the given value.
34
74
  * - `job` alone matches a `job`/`workflow`-scoped hold for that job.
75
+ * - `holdType` narrows whichever set the above produced, and may be used on its
76
+ * own.
35
77
  * - With no filter, the sole pending hold is used; ambiguity is an error.
78
+ *
79
+ * One job can carry more than one pending hold: an SDK `requireApproval` paired
80
+ * with a security-typed context gate writes two job-scoped rows under one job
81
+ * name, because two independent requirements gate the job and both must be
82
+ * answered. `job` alone cannot separate those, which is what `holdType` and
83
+ * `holdId` are for.
36
84
  */
37
85
  function resolveHeldRunId(holds, filter) {
38
86
  const pending = holds.filter((h) => h.status === HeldRunStatus.enum.pending);
@@ -40,17 +88,22 @@ function resolveHeldRunId(holds, filter) {
40
88
  ok: false,
41
89
  error: "No pending approval holds found for this run."
42
90
  };
91
+ if (filter.holdId !== void 0) return pickSingle(pending.filter((h) => h.id === filter.holdId), `hold '${filter.holdId}'`, pending);
92
+ const wanted = filter.holdType && normalizePersistedHoldType(filter.holdType);
93
+ const byType = (candidates) => wanted === void 0 ? candidates : candidates.filter((h) => h.holdType && normalizePersistedHoldType(h.holdType) === wanted);
94
+ const typeLabel = wanted ? ` of type '${wanted}'` : "";
43
95
  if (filter.step !== void 0) {
44
96
  if (!filter.job) return {
45
97
  ok: false,
46
98
  error: "--step requires --job to identify the held step."
47
99
  };
48
- return pickSingle(pending.filter((h) => h.holdScope === "step" && h.jobId === filter.job && String(h.stepIndex ?? "") === filter.step), `step ${filter.step} of job '${filter.job}'`, pending);
100
+ return pickSingle(byType(pending.filter((h) => h.holdScope === "step" && h.jobId === filter.job && String(h.stepIndex ?? "") === filter.step)), `step ${filter.step} of job '${filter.job}'${typeLabel}`, pending);
49
101
  }
50
- if (filter.job !== void 0) return pickSingle(pending.filter((h) => h.jobId === filter.job && h.holdScope !== "step"), `job '${filter.job}'`, pending);
102
+ if (filter.job !== void 0) return pickSingle(byType(pending.filter((h) => h.jobId === filter.job && h.holdScope !== "step")), `job '${filter.job}'${typeLabel}`, pending);
103
+ if (wanted !== void 0) return pickSingle(byType(pending), `hold type '${wanted}'`, pending);
51
104
  if (pending.length > 1) return {
52
105
  ok: false,
53
- error: `Multiple pending holds for this run. Use --job <name> (and --step <index>) to choose one. Candidates: ${listCandidates(pending)}.`
106
+ error: `Multiple pending holds for this run. Use ${DISAMBIGUATORS} to choose one. Candidates: ${listCandidates(pending)}.`
54
107
  };
55
108
  return {
56
109
  ok: true,
@@ -65,7 +118,7 @@ function pickSingle(matches, label, pending) {
65
118
  };
66
119
  if (matches.length > 1) return {
67
120
  ok: false,
68
- error: `Multiple pending holds match ${label}; cannot disambiguate. Candidates: ${listCandidates(matches)}.`
121
+ error: `Multiple pending holds match ${label}. Narrow it with ${DISAMBIGUATORS}. Candidates: ${listCandidates(matches)}.`
69
122
  };
70
123
  return {
71
124
  ok: true,
@@ -59,12 +59,16 @@ export declare const approveRunToolSchema: {
59
59
  runId: z.ZodString;
60
60
  job: z.ZodOptional<z.ZodString>;
61
61
  step: z.ZodOptional<z.ZodString>;
62
+ holdType: z.ZodOptional<z.ZodString>;
63
+ holdId: z.ZodOptional<z.ZodString>;
62
64
  };
63
65
  export declare const rejectRunToolSchema: {
64
66
  orgId: z.ZodOptional<z.ZodString>;
65
67
  runId: z.ZodString;
66
68
  job: z.ZodOptional<z.ZodString>;
67
69
  step: z.ZodOptional<z.ZodString>;
70
+ holdType: z.ZodOptional<z.ZodString>;
71
+ holdId: z.ZodOptional<z.ZodString>;
68
72
  reason: z.ZodString;
69
73
  };
70
74
  export declare const cancelRunsByBranchToolSchema: {
@@ -55,17 +55,29 @@ const triggerRunToolSchema = {
55
55
  orgId: orgIdArg,
56
56
  registrationId: z.string().min(1).describe("The workflow registration id to trigger.")
57
57
  };
58
+ /**
59
+ * The two filters that separate holds a job name cannot. One job can carry more
60
+ * than one pending hold — an SDK `requireApproval` paired with a security-typed
61
+ * context gate writes two job-scoped rows under one job name — so `job` alone is
62
+ * ambiguous for that shape, and the error listing names these instead.
63
+ */
64
+ const holdTypeArg = z.string().optional().describe("Hold type (reviewer, timer, concurrency, security), when one job carries more than one hold.");
65
+ const holdIdArg = z.string().optional().describe("A specific hold id, as printed in the candidate list when nothing else separates them.");
58
66
  const approveRunToolSchema = {
59
67
  orgId: orgIdArg,
60
68
  runId: z.string().min(1).describe("The run whose held approval gate to approve."),
61
69
  job: z.string().optional().describe("Job name, to disambiguate when the run has multiple holds."),
62
- step: z.string().optional().describe("Zero-based step index (requires job) for a step-scoped hold.")
70
+ step: z.string().optional().describe("Zero-based step index (requires job) for a step-scoped hold."),
71
+ holdType: holdTypeArg,
72
+ holdId: holdIdArg
63
73
  };
64
74
  const rejectRunToolSchema = {
65
75
  orgId: orgIdArg,
66
76
  runId: z.string().min(1).describe("The run whose held approval gate to reject."),
67
77
  job: z.string().optional().describe("Job name, to disambiguate when the run has multiple holds."),
68
78
  step: z.string().optional().describe("Zero-based step index (requires job) for a step-scoped hold."),
79
+ holdType: holdTypeArg,
80
+ holdId: holdIdArg,
69
81
  reason: z.string().min(1).describe("Why the gate is rejected (required).")
70
82
  };
71
83
  const cancelRunsByBranchToolSchema = {
@@ -55,10 +55,13 @@ export declare const ORCH_PUSHED_METRIC_NAMES: ReadonlySet<MetricName>;
55
55
  /**
56
56
  * Closed enum of scaler-backend TYPE values the orchestrator's own
57
57
  * resource-usage gauges carry on their `scalerType` rollup label.
58
- * `__global__` is the orchestrator-wide rollup row; the other four match
59
- * `AGENT_SCALER_VALUES`. Exported so the policy tests can assert against it.
58
+ * `__global__` is the orchestrator-wide rollup row; every other value comes
59
+ * from `ScalerManager`'s own `backends` map, so the enum is `ScalerBackendType`
60
+ * itself — NOT `AGENT_SCALER_VALUES`, which additionally carries the
61
+ * agent-only `stateful` and predates the `event` backend. Exported so the
62
+ * policy tests can assert against it.
60
63
  */
61
- export declare const ORCH_SCALER_VALUES: readonly ["__global__", "stateful", "container", "firecracker", "bare-metal"];
64
+ export declare const ORCH_SCALER_VALUES: readonly ["__global__", ...("bare-metal" | "container" | "event" | "firecracker" | "kubernetes")[]];
62
65
  /**
63
66
  * Closed enum of the scheduled jobs the orchestrator runs. MUST mirror
64
67
  * `OrchestratorScheduledJobName` (`packages/orchestrator/src/queue/scheduled-job.ts`)
@@ -1,5 +1,6 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { ExecutionRunStatus } from "../protocol/messages/execution-status.js";
3
+ import { ScalerBackendType } from "../scaler/scaler-backend-type.js";
3
4
  import { MetricNames, MetricService } from "./metric-catalog.generated.js";
4
5
  //#region src/metrics/catalog-policy.ts
5
6
  /**
@@ -41,20 +42,26 @@ const OVERFLOW_LABEL_VALUE = "__overflow__";
41
42
  * so they are valid push payloads alongside `kici_orch_*`.
42
43
  */
43
44
  const ORCH_PUSHED_METRIC_NAMES = new Set(Object.entries(MetricService).filter(([, svc]) => svc === "orchestrator" || svc === "agent" || svc === "runtime").map(([key]) => MetricNames[key]));
44
- /** Closed enum of the four scaler-backend label values an agent can carry. */
45
- const AGENT_SCALER_VALUES = [
46
- "stateful",
47
- "container",
48
- "firecracker",
49
- "bare-metal"
50
- ];
45
+ /**
46
+ * Closed enum of the `scaler` label values an agent can carry: every scaler
47
+ * backend type the orchestrator can resolve for a managed agent, plus
48
+ * `stateful` for a static agent no scaler manages (the fallback
49
+ * `AgentMetricsAggregator.resolveScalerLabel` stamps when the lookup returns
50
+ * null). Derived from `ScalerBackendType` rather than restated, so adding a
51
+ * backend cannot silently drop every `kici_agent_*` series that backend's
52
+ * agents emit as `bad_label_value`.
53
+ */
54
+ const AGENT_SCALER_VALUES = ["stateful", ...ScalerBackendType.options];
51
55
  /**
52
56
  * Closed enum of scaler-backend TYPE values the orchestrator's own
53
57
  * resource-usage gauges carry on their `scalerType` rollup label.
54
- * `__global__` is the orchestrator-wide rollup row; the other four match
55
- * `AGENT_SCALER_VALUES`. Exported so the policy tests can assert against it.
58
+ * `__global__` is the orchestrator-wide rollup row; every other value comes
59
+ * from `ScalerManager`'s own `backends` map, so the enum is `ScalerBackendType`
60
+ * itself — NOT `AGENT_SCALER_VALUES`, which additionally carries the
61
+ * agent-only `stateful` and predates the `event` backend. Exported so the
62
+ * policy tests can assert against it.
56
63
  */
57
- const ORCH_SCALER_VALUES = ["__global__", ...AGENT_SCALER_VALUES];
64
+ const ORCH_SCALER_VALUES = ["__global__", ...ScalerBackendType.options];
58
65
  /**
59
66
  * Closed enum of the scheduled jobs the orchestrator runs. MUST mirror
60
67
  * `OrchestratorScheduledJobName` (`packages/orchestrator/src/queue/scheduled-job.ts`)
@@ -146,6 +153,20 @@ const METRIC_LABEL_POLICY = {
146
153
  scalerType: { values: ORCH_SCALER_VALUES },
147
154
  machinePool: { maxUniqueValues: 50 }
148
155
  },
156
+ kici_orch_scaler_warm_pool_target: {
157
+ scaler: { maxUniqueValues: 50 },
158
+ labelSet: { maxUniqueValues: 100 }
159
+ },
160
+ kici_orch_scaler_warm_pool_ready: {
161
+ scaler: { maxUniqueValues: 50 },
162
+ labelSet: { maxUniqueValues: 100 }
163
+ },
164
+ kici_orch_scaler_warm_pool_in_flight: {
165
+ scaler: { maxUniqueValues: 50 },
166
+ labelSet: { maxUniqueValues: 100 }
167
+ },
168
+ kici_orch_scaler_warm_pool_spawns_total: { scaler: { maxUniqueValues: 50 } },
169
+ kici_orch_scaler_warm_pool_reaped_total: { scaler: { maxUniqueValues: 50 } },
149
170
  kici_orch_install_secrets_decisions_total: {
150
171
  decision: { values: [
151
172
  "pass",