@kici-dev/compiler 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/auth/headless-detect.js +1 -1
  2. package/dist/cli-banner.js +1 -1
  3. package/dist/cli.js +56 -19
  4. package/dist/commands/approve.js +1 -1
  5. package/dist/commands/compile.js +19 -17
  6. package/dist/commands/diagnostics.d.ts +7 -0
  7. package/dist/commands/diagnostics.js +58 -0
  8. package/dist/commands/docs.js +1 -1
  9. package/dist/commands/drain-worker.js +1 -1
  10. package/dist/commands/endpoints.js +1 -1
  11. package/dist/commands/fixture.js +1 -1
  12. package/dist/commands/held-run-client.js +1 -1
  13. package/dist/commands/held-run-resolve.js +1 -1
  14. package/dist/commands/hook.js +1 -1
  15. package/dist/commands/index.d.ts +16 -4
  16. package/dist/commands/index.js +10 -4
  17. package/dist/commands/init.js +2 -2
  18. package/dist/commands/login.d.ts +0 -2
  19. package/dist/commands/login.js +1 -3
  20. package/dist/commands/logout.js +1 -1
  21. package/dist/commands/orchestrators.d.ts +25 -0
  22. package/dist/commands/orchestrators.js +115 -0
  23. package/dist/commands/org.js +1 -1
  24. package/dist/commands/reject.js +1 -1
  25. package/dist/commands/run.d.ts +2 -9
  26. package/dist/commands/run.js +256 -315
  27. package/dist/commands/runs/cancel.d.ts +6 -0
  28. package/dist/commands/runs/cancel.js +32 -0
  29. package/dist/commands/runs/list.d.ts +6 -0
  30. package/dist/commands/runs/list.js +49 -0
  31. package/dist/commands/runs/logs.d.ts +7 -0
  32. package/dist/commands/runs/logs.js +67 -0
  33. package/dist/commands/runs/rerun.d.ts +5 -0
  34. package/dist/commands/runs/rerun.js +21 -0
  35. package/dist/commands/runs/show.d.ts +5 -0
  36. package/dist/commands/runs/show.js +65 -0
  37. package/dist/commands/secrets-list.d.ts +7 -9
  38. package/dist/commands/secrets-list.js +40 -64
  39. package/dist/commands/test.d.ts +4 -0
  40. package/dist/commands/test.js +4 -6
  41. package/dist/commands/types.d.ts +3 -5
  42. package/dist/commands/types.js +13 -37
  43. package/dist/commands/verify-attestation.d.ts +12 -0
  44. package/dist/commands/verify-attestation.js +69 -0
  45. package/dist/commands/watch.js +1 -1
  46. package/dist/commands/workflows.js +14 -34
  47. package/dist/errors/capability-gap.js +1 -1
  48. package/dist/errors/formatter.js +1 -1
  49. package/dist/errors/index.js +1 -1
  50. package/dist/execution/executor.js +1 -1
  51. package/dist/execution/index.js +1 -1
  52. package/dist/execution/sdk-alias.js +1 -1
  53. package/dist/execution/ts-loader.js +1 -1
  54. package/dist/fixtures/compiler.js +1 -1
  55. package/dist/fixtures/defaults/index.js +1 -1
  56. package/dist/format.js +1 -1
  57. package/dist/generators/secrets-dts.js +2 -2
  58. package/dist/hooks/detector.js +1 -1
  59. package/dist/hooks/index.js +1 -1
  60. package/dist/hooks/installer.js +1 -1
  61. package/dist/hooks/templates.js +1 -1
  62. package/dist/index.js +1 -1
  63. package/dist/llm-context/llms-full.txt +1549 -1282
  64. package/dist/llm-context/llms.txt +6 -2
  65. package/dist/local-executor/dag-scheduler.js +1 -1
  66. package/dist/local-executor/index.js +1 -1
  67. package/dist/local-executor/job-runner.js +40 -18
  68. package/dist/local-executor/materializer.js +1 -1
  69. package/dist/local-executor/output-streamer.js +1 -1
  70. package/dist/local-executor/payload-generator.js +1 -1
  71. package/dist/local-executor/picker.js +1 -1
  72. package/dist/local-executor/runs-on-display.d.ts +9 -0
  73. package/dist/local-executor/runs-on-display.js +23 -0
  74. package/dist/local-executor/secret-loader.js +1 -1
  75. package/dist/local-executor/to-event-payload.js +1 -1
  76. package/dist/local-executor/types.js +1 -1
  77. package/dist/local-executor/workflow-lock.js +0 -0
  78. package/dist/lockfile/generator.d.ts +9 -5
  79. package/dist/lockfile/generator.js +58 -38
  80. package/dist/lockfile/hash-files.js +1 -1
  81. package/dist/lockfile/hasher.js +1 -1
  82. package/dist/lockfile/index.js +1 -1
  83. package/dist/lockfile/purity-analyzer.js +1 -1
  84. package/dist/postinstall.js +1 -1
  85. package/dist/provenance-trust-root.d.ts +14 -0
  86. package/dist/provenance-trust-root.js +49 -0
  87. package/dist/remote/config.d.ts +6 -2
  88. package/dist/remote/config.js +7 -4
  89. package/dist/remote/dashboard-client.d.ts +92 -0
  90. package/dist/remote/dashboard-client.js +168 -0
  91. package/dist/remote/encryption.js +3 -4
  92. package/dist/remote/history.js +1 -1
  93. package/dist/remote/oauth.js +1 -1
  94. package/dist/remote/oidc-discovery.js +1 -1
  95. package/dist/remote/output/json.js +1 -1
  96. package/dist/remote/output/junit.js +1 -1
  97. package/dist/remote/output/streaming.js +1 -1
  98. package/dist/remote/output/summary.js +1 -1
  99. package/dist/remote/platform-client.d.ts +142 -0
  100. package/dist/remote/platform-client.js +187 -0
  101. package/dist/remote/prod-defaults.js +1 -1
  102. package/dist/remote/render.d.ts +11 -0
  103. package/dist/remote/render.js +48 -0
  104. package/dist/remote/secret-upload.js +1 -1
  105. package/dist/remote/uploader.d.ts +9 -0
  106. package/dist/remote/uploader.js +51 -4
  107. package/dist/templates/agents-md.d.ts +1 -1
  108. package/dist/templates/agents-md.js +8 -3
  109. package/dist/templates/index.js +1 -1
  110. package/dist/templates/package-json.js +4 -5
  111. package/dist/templates/tsconfig-json.js +1 -1
  112. package/dist/templates/workflows/hello-world.js +2 -2
  113. package/dist/templates/workflows/hello-world.ts +1 -1
  114. package/dist/templates/workflows/pr-checks.js +3 -3
  115. package/dist/templates/workflows/pr-checks.ts +2 -2
  116. package/dist/test-runner/dry-run.js +1 -1
  117. package/dist/test-runner/event-types.js +1 -1
  118. package/dist/test-runner/git-detector.js +1 -1
  119. package/dist/test-runner/index.js +1 -1
  120. package/dist/test-runner/job-executor.js +3 -2
  121. package/dist/test-runner/output-formatter.js +1 -1
  122. package/dist/test-runner/payload-builder.js +1 -1
  123. package/dist/test-runner/rule-evaluator.js +1 -1
  124. package/dist/test-runner/secrets-file.js +1 -1
  125. package/dist/test-runner/step-context.js +10 -6
  126. package/dist/types.d.ts +29 -8
  127. package/dist/types.js +2 -1
  128. package/dist/validation/index.js +1 -1
  129. package/dist/validation/validator.js +1 -1
  130. package/dist/workflows/hello-world.ts +1 -1
  131. package/dist/workflows/pr-checks.ts +2 -2
  132. package/package.json +10 -9
  133. package/sbom.spdx.json +465 -410
  134. package/dist/commands/cancel.js +0 -124
  135. package/dist/commands/status.js +0 -212
  136. package/dist/remote/client.js +0 -203
  137. package/dist/remote/observer.js +0 -174
  138. /package/dist/{chunk-gOLHoazu.js → chunk-BTugEXQM.js} +0 -0
@@ -25,6 +25,7 @@ The full markdown bundle of every page indexed here is available at https://docs
25
25
  - [Event payload reference](https://docs.kici.dev/user/sdk/event-payloads/): Generated schema of the normalized event envelope passed to rules and dynamic functions.
26
26
  - [SDK reference: idempotent](https://docs.kici.dev/user/sdk/idempotent/): Idempotent helpers for declarative check / apply patterns inside workflow steps
27
27
  - [SDK reference: rules, matrix, dynamic jobs](https://docs.kici.dev/user/sdk/rules-matrix-dynamic/): rule(), skip(), matrix builds (static + dynamic), and dynamicJob / dynamicGroup
28
+ - [SDK reference: runsOnAll host fan-out](https://docs.kici.dev/user/sdk/runs-on-all/): Fan one job out to every matching connected host, one pinned execution per host
28
29
  - [SDK reference: runtime](https://docs.kici.dev/user/sdk/runtime/): Types index, StepContext, secrets, fixtures
29
30
  - [SDK reference: triggers](https://docs.kici.dev/user/sdk/triggers/): GitHub event triggers, kiciEvent, workflowComplete, jobComplete, genericWebhook, schedule, lifecycle
30
31
  - [SDK reference: validation & events](https://docs.kici.dev/user/sdk/validation-events/): validateDag(), event definitions, and event emission
@@ -34,7 +35,7 @@ The full markdown bundle of every page indexed here is available at https://docs
34
35
  ## CLI and authoring
35
36
 
36
37
  - [CLI authentication](https://docs.kici.dev/user/cli-auth/): Authenticate the KiCI CLI with browser OAuth, device flow, or API key paste
37
- - [CLI reference](https://docs.kici.dev/user/cli-reference/): All CLI commands: compile, run (local/remote), test, login, logout, org, status, cancel, secrets, types, fixture, init, hook, endpoints, workflows, docs, admin
38
+ - [CLI reference](https://docs.kici.dev/user/cli-reference/): All CLI commands: compile, run (local/remote), orchestrators, test, login, logout, org, diagnostics, runs (list/show/logs/rerun/cancel), secrets, types, fixture, init, hook, endpoints, workflows, docs, admin
38
39
  - [Lifecycle hooks](https://docs.kici.dev/user/hooks/): SDK hook API for cancel, cleanup, success, failure, and step-level callbacks
39
40
  - [Lock file and workflow drift](https://docs.kici.dev/user/lock-file-and-drift/): Keep the lock file in sync with your workflow source and avoid drift
40
41
  - [Testing guide](https://docs.kici.dev/user/testing-guide/): Running remote test fixtures with kici run remote
@@ -43,19 +44,22 @@ The full markdown bundle of every page indexed here is available at https://docs
43
44
  ## Workflow features
44
45
 
45
46
  - [Account and sign-in](https://docs.kici.dev/user/account-and-login/): How your KiCI account relates to sign-in methods, and how to change the way you sign in.
47
+ - [Approval gates](https://docs.kici.dev/user/approvals/): Pause a workflow for human sign-off at step, job, or workflow granularity with requireApproval
46
48
  - [Concurrency groups](https://docs.kici.dev/user/concurrency/): Control parallel execution with auto-cancel and queue modes
47
- - [Dashboard](https://docs.kici.dev/user/dashboard/)
49
+ - [Dashboard](https://docs.kici.dev/user/dashboard/): Web UI for monitoring workflow runs, managing sources, secrets, and organization settings.
48
50
  - [Dynamic values](https://docs.kici.dev/user/dynamic-values/)
49
51
  - [Environment variables](https://docs.kici.dev/user/env-vars/): KICI_* environment variable reference for the CLI
50
52
  - [Environments](https://docs.kici.dev/user/environments/): Configure deployment environments with variables, secrets, and protection rules
51
53
  - [Event system](https://docs.kici.dev/user/events/): How KiCI's event model works -- event types, the registration model, event matching, and circuit breaker protection
52
54
  - [Global workflows](https://docs.kici.dev/user/global-workflows/): Cross-repo workflows that run on events from any repo in the same org
53
55
  - [Private npm registries](https://docs.kici.dev/user/private-registries/): Authenticate `npm install` against private registries (CodeArtifact, GitHub Packages, Verdaccio, …) from a workflow's `.kici/package.json`
56
+ - [Build provenance and attestations](https://docs.kici.dev/user/provenance/): Generate and verify signed SLSA provenance for the artifacts your workflows build
54
57
  - [Secrets](https://docs.kici.dev/user/secrets/): How to access secrets in KiCI workflow steps
55
58
 
56
59
  ## Providers
57
60
 
58
61
  - [GitHub App provider](https://docs.kici.dev/user/providers/github/): Connect KiCI to GitHub via a GitHub App — full Checks API, installation-token clones, and cross-org dispatch
62
+ - [Local filesystem (file://) source](https://docs.kici.dev/user/providers/local-file/): Register a git repo present on the agent filesystem as a file:// source, triggered by the kici-admin CLI or a push hook
59
63
  - [Universal-git provider](https://docs.kici.dev/user/providers/universal-git/): Connect Forgejo, Gitea, Gogs, GitLab, or plain GitHub via webhook + PAT or SSH deploy key
60
64
 
61
65
  ## Architecture overview
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  //#region src/local-executor/dag-scheduler.ts
3
3
  /**
4
4
  * Detect cycles using Kahn's algorithm.
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import { discoverWorkflows, resolveKiciDir } from "../execution/executor.js";
3
3
  import "../execution/index.js";
4
4
  import { transformTriggers } from "../lockfile/generator.js";
@@ -1,12 +1,14 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import { ensureTsLoaderHook } from "../execution/ts-loader.js";
3
3
  import { formatter } from "../test-runner/output-formatter.js";
4
4
  import { createStepContext } from "../test-runner/step-context.js";
5
5
  import { createRuleContext, evaluateRules as evaluateRulesWithFormatting } from "../test-runner/rule-evaluator.js";
6
+ import { localRunsOnString } from "./runs-on-display.js";
6
7
  import { toEventPayload } from "./to-event-payload.js";
7
8
  import { pathToFileURL } from "node:url";
8
9
  import path from "node:path";
9
10
  import { applyIncludeExclude, expandMatrix, isDynamicJobFn, setJobOutputsMap, setStepOutputsMap, setStepRefMap } from "@kici-dev/sdk";
11
+ import { formatMatrixSuffix } from "@kici-dev/engine";
10
12
  //#region src/local-executor/job-runner.ts
11
13
  /**
12
14
  * Job resolution (matrix/dynamic) and single-job execution with step context.
@@ -39,15 +41,6 @@ async function resolveSdkSetters(kiciDir) {
39
41
  };
40
42
  }
41
43
  /**
42
- * Format matrix values into a display string for the job name.
43
- * Single-dimension: "node-18"
44
- * Multi-dimensional: "linux, 18"
45
- */
46
- function formatMatrixSuffix(matrixValues) {
47
- if ("value" in matrixValues && matrixValues.value !== void 0) return matrixValues.value;
48
- return Object.values(matrixValues).filter((v) => v !== void 0).join(", ");
49
- }
50
- /**
51
44
  * Resolve a needs entry to a string name.
52
45
  * Handles Job objects, strings, DynamicGroupRef, and object forms.
53
46
  */
@@ -58,6 +51,27 @@ function resolveNeedName(need) {
58
51
  return need.name;
59
52
  }
60
53
  /**
54
+ * Maintain the base-name `{ byMatrix, merged }` envelope as each matrix child
55
+ * completes. `merged` is rebuilt last-write-wins in suffix order so the result
56
+ * is deterministic regardless of child completion order — matching the remote
57
+ * path's name-ordered merge.
58
+ */
59
+ function updateBaseMatrixEnvelope(jobOutputsMap, baseName, matrixValues, childOutputs) {
60
+ const existing = jobOutputsMap.get(baseName);
61
+ const byMatrix = existing && "byMatrix" in existing ? { ...existing.byMatrix } : {};
62
+ const suffix = formatMatrixSuffix(matrixValues);
63
+ byMatrix[suffix] = childOutputs;
64
+ let merged = {};
65
+ for (const key of Object.keys(byMatrix).sort()) merged = {
66
+ ...merged,
67
+ ...byMatrix[key]
68
+ };
69
+ jobOutputsMap.set(baseName, {
70
+ byMatrix,
71
+ merged
72
+ });
73
+ }
74
+ /**
61
75
  * Resolve all jobs in a workflow, expanding matrix jobs and evaluating dynamic jobs.
62
76
  *
63
77
  * - Static jobs: create ResolvedJob with name, needs, job reference
@@ -82,10 +96,13 @@ async function resolveJobs(workflow, event) {
82
96
  debug: () => {}
83
97
  },
84
98
  env: { ...process.env },
85
- kici: { infrastructure: { list: () => Promise.resolve({
86
- scalers: [],
87
- agents: []
88
- }) } }
99
+ kici: {
100
+ infrastructure: { list: () => Promise.resolve({
101
+ scalers: [],
102
+ agents: []
103
+ }) },
104
+ oidc: { token: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.oidc.token() is not available during local execution")) }
105
+ }
89
106
  });
90
107
  for (const genJob of generatedJobs) {
91
108
  const genResolved = await resolveStaticJob(genJob, expansionMap);
@@ -119,7 +136,7 @@ async function resolveStaticJob(job, expansionMap) {
119
136
  workflow: { name: "" },
120
137
  job: {
121
138
  name: job.name,
122
- runsOn: job.runsOn
139
+ runsOn: localRunsOnString(job.runsOn)
123
140
  }
124
141
  },
125
142
  log: {
@@ -219,7 +236,7 @@ async function executeResolvedJobInner(resolvedJob, context, startTime) {
219
236
  const repoRoot = context.execDir;
220
237
  const stepCtx = createStepContext({ name: context.workflowName }, {
221
238
  name: expandedName,
222
- runsOn: typeof job.runsOn === "string" ? job.runsOn : Array.isArray(job.runsOn) ? job.runsOn.join(",") : (Array.isArray(job.runsOn.labels) ? job.runsOn.labels : [job.runsOn.labels]).join(",")
239
+ runsOn: localRunsOnString(job.runsOn)
223
240
  }, repoRoot, void 0, hasMatrix ? matrixValues : void 0, context.secrets, void 0, context.event.payload, context.event.provider);
224
241
  const stepResults = [];
225
242
  let stepCounter = 0;
@@ -285,11 +302,16 @@ async function executeResolvedJobInner(resolvedJob, context, startTime) {
285
302
  }
286
303
  }
287
304
  const stepsWithOutputs = stepResults.filter((s) => s.outputs != null);
288
- if (stepsWithOutputs.length === 1 && stepResults.length === 1) context.jobOutputsMap.set(expandedName, stepsWithOutputs[0].outputs);
305
+ let jobOutputs;
306
+ if (stepsWithOutputs.length === 1 && stepResults.length === 1) jobOutputs = stepsWithOutputs[0].outputs;
289
307
  else if (stepsWithOutputs.length > 0) {
290
308
  const aggregated = {};
291
309
  for (const stepResult of stepsWithOutputs) aggregated[stepResult.name] = stepResult.outputs;
292
- context.jobOutputsMap.set(expandedName, aggregated);
310
+ jobOutputs = aggregated;
311
+ }
312
+ if (jobOutputs) {
313
+ context.jobOutputsMap.set(expandedName, jobOutputs);
314
+ if (expandedName !== job.name) updateBaseMatrixEnvelope(context.jobOutputsMap, job.name, matrixValues, jobOutputs);
293
315
  }
294
316
  const durationMs = Date.now() - startTime;
295
317
  formatter.logJobComplete(expandedName, durationMs);
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import { selectOverlayFiles } from "../remote/uploader.js";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs/promises";
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import pc from "picocolors";
3
3
  import { logger } from "@kici-dev/core";
4
4
  //#region src/local-executor/output-streamer.ts
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import { parseEventArg } from "../test-runner/event-types.js";
3
3
  import { buildEventPayload } from "../test-runner/payload-builder.js";
4
4
  import { execSync } from "node:child_process";
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import { transformTriggers } from "../lockfile/generator.js";
3
3
  import { parseEventArg, triggerSummary, triggerToEventArg } from "../test-runner/event-types.js";
4
4
  import pc from "picocolors";
@@ -0,0 +1,9 @@
1
+ import type { RunsOn } from '@kici-dev/sdk';
2
+ /**
3
+ * Render a job's `runsOn` to the comma-joined label string the local/test
4
+ * runners surface as `ctx.job.runsOn`. A `runsOnAll` job has no `runsOn` —
5
+ * locally it runs once on this machine, so we surface the marker `'runsOnAll'`.
6
+ * RegExp selectors render via their `/source/flags` literal.
7
+ */
8
+ export declare function localRunsOnString(runsOn: RunsOn | undefined): string;
9
+ //# sourceMappingURL=runs-on-display.d.ts.map
@@ -0,0 +1,23 @@
1
+ import "../chunk-BTugEXQM.js";
2
+ //#region src/local-executor/runs-on-display.ts
3
+ /** Render one selector element (string label or RegExp) for the local display string. */
4
+ function runsOnElementString(el) {
5
+ return el instanceof RegExp ? el.toString() : el;
6
+ }
7
+ /**
8
+ * Render a job's `runsOn` to the comma-joined label string the local/test
9
+ * runners surface as `ctx.job.runsOn`. A `runsOnAll` job has no `runsOn` —
10
+ * locally it runs once on this machine, so we surface the marker `'runsOnAll'`.
11
+ * RegExp selectors render via their `/source/flags` literal.
12
+ */
13
+ function localRunsOnString(runsOn) {
14
+ if (runsOn === void 0) return "runsOnAll";
15
+ if (typeof runsOn === "string") return runsOn;
16
+ if (runsOn instanceof RegExp) return runsOn.toString();
17
+ if (Array.isArray(runsOn)) return runsOn.map(runsOnElementString).join(",");
18
+ return (Array.isArray(runsOn.labels) ? runsOn.labels : [runsOn.labels]).map(runsOnElementString).join(",");
19
+ }
20
+ //#endregion
21
+ export { localRunsOnString };
22
+
23
+ //# sourceMappingURL=runs-on-display.js.map
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import { loadSecretsFile } from "../test-runner/secrets-file.js";
3
3
  import path from "node:path";
4
4
  import { readFile } from "node:fs/promises";
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  //#region src/local-executor/to-event-payload.ts
3
3
  /**
4
4
  * Map a {@link SimulatedEvent} into the normalized SDK {@link EventPayload}
@@ -1,2 +1,2 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  export {};
@@ -13,11 +13,15 @@ export declare function detectGitRoot(): string;
13
13
  *
14
14
  * The authoritative lockfile differs by manager: `.kici/package-lock.json` for
15
15
  * npm, the repo-root `pnpm-lock.yaml` for a pnpm workspace, the repo-root
16
- * `yarn.lock` for yarn. A pnpm/yarn `.kici/` member resolves against the root
17
- * lockfile, so keying on `.kici/package-lock.json` alone would miss the
18
- * authoritative graph. The hash input is prefixed with the manager name so two
19
- * managers can never collide on identical lockfile bytes (and a restored tree
20
- * can never be the wrong layout for the agent's manager).
16
+ * `yarn.lock` for a yarn workspace. A pnpm/yarn `.kici/` member resolves against
17
+ * the root lockfile, so keying on `.kici/package-lock.json` alone would miss the
18
+ * authoritative graph. A standalone `.kici` yarn project (its own
19
+ * `packageManager` field + `.kici/yarn.lock`, no root signal) is detected from
20
+ * `.kici/` and keyed on `.kici/yarn.lock`. The hash input is prefixed with the
21
+ * manager name so two managers can never collide on identical lockfile bytes
22
+ * (and a restored tree can never be the wrong layout for the agent's manager).
23
+ * For yarn the prefix also carries the flavor (`yarn-classic` / `yarn-berry`),
24
+ * so a classic-layout dep-cache tarball is never restored into a berry install.
21
25
  *
22
26
  * @param gitRoot - Absolute path to git repository root
23
27
  * @returns Hex SHA-256 hash string, or null if no lockfile is found
@@ -1,14 +1,15 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import { SCHEMA_VERSION as SCHEMA_VERSION$1 } from "../types.js";
3
3
  import { computeContentHash } from "./hasher.js";
4
4
  import { resolveHashFiles } from "./hash-files.js";
5
5
  import { analyzePurity } from "./purity-analyzer.js";
6
6
  import path from "node:path";
7
7
  import { readFileSync } from "node:fs";
8
- import { getDynamicJobGroup, isDynamicFunction, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject, normalizeCacheSpecs, normalizeRequireApproval } from "@kici-dev/sdk";
8
+ import { getDynamicJobGroup, getDynamicJobNeeds, isDynamicFunction, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject, normalizeCacheSpecs, normalizeRequireApproval } from "@kici-dev/sdk";
9
9
  import { sha256 } from "@kici-dev/core";
10
- import { PackageManager, detectPackageManagerSync } from "@kici-dev/core/package-manager";
10
+ import { PackageManager, detectPackageManagerSync, detectYarnFlavorSync } from "@kici-dev/core/package-manager";
11
11
  import { validateResourceRequest } from "@kici-dev/engine";
12
+ import { normalizeRunsOnAllToMatchers, normalizeRunsOnToMatchers } from "@kici-dev/engine/labels/compile";
12
13
  import { execSync } from "node:child_process";
13
14
  //#region src/lockfile/generator.ts
14
15
  /**
@@ -37,23 +38,27 @@ function detectGitRoot() {
37
38
  *
38
39
  * The authoritative lockfile differs by manager: `.kici/package-lock.json` for
39
40
  * npm, the repo-root `pnpm-lock.yaml` for a pnpm workspace, the repo-root
40
- * `yarn.lock` for yarn. A pnpm/yarn `.kici/` member resolves against the root
41
- * lockfile, so keying on `.kici/package-lock.json` alone would miss the
42
- * authoritative graph. The hash input is prefixed with the manager name so two
43
- * managers can never collide on identical lockfile bytes (and a restored tree
44
- * can never be the wrong layout for the agent's manager).
41
+ * `yarn.lock` for a yarn workspace. A pnpm/yarn `.kici/` member resolves against
42
+ * the root lockfile, so keying on `.kici/package-lock.json` alone would miss the
43
+ * authoritative graph. A standalone `.kici` yarn project (its own
44
+ * `packageManager` field + `.kici/yarn.lock`, no root signal) is detected from
45
+ * `.kici/` and keyed on `.kici/yarn.lock`. The hash input is prefixed with the
46
+ * manager name so two managers can never collide on identical lockfile bytes
47
+ * (and a restored tree can never be the wrong layout for the agent's manager).
48
+ * For yarn the prefix also carries the flavor (`yarn-classic` / `yarn-berry`),
49
+ * so a classic-layout dep-cache tarball is never restored into a berry install.
45
50
  *
46
51
  * @param gitRoot - Absolute path to git repository root
47
52
  * @returns Hex SHA-256 hash string, or null if no lockfile is found
48
53
  */
49
54
  function computeLockfileHash(gitRoot) {
50
- const pm = detectPackageManagerSync(gitRoot);
51
- const lockfilePath = pm === PackageManager.Pnpm ? path.join(gitRoot, "pnpm-lock.yaml") : pm === PackageManager.Yarn ? path.join(gitRoot, "yarn.lock") : path.join(gitRoot, ".kici", "package-lock.json");
52
- try {
53
- return sha256(`${pm}\n${readFileSync(lockfilePath, "utf-8")}`);
54
- } catch {
55
- return null;
56
- }
55
+ const pm = detectPackageManagerSync(gitRoot) === PackageManager.Npm ? detectPackageManagerSync(path.join(gitRoot, ".kici")) : detectPackageManagerSync(gitRoot);
56
+ const candidates = pm === PackageManager.Pnpm ? [path.join(gitRoot, "pnpm-lock.yaml"), path.join(gitRoot, ".kici", "pnpm-lock.yaml")] : pm === PackageManager.Yarn ? [path.join(gitRoot, "yarn.lock"), path.join(gitRoot, ".kici", "yarn.lock")] : [path.join(gitRoot, ".kici", "package-lock.json")];
57
+ const prefix = pm === PackageManager.Yarn ? `${pm}-${detectYarnFlavorSync(detectPackageManagerSync(gitRoot) === PackageManager.Yarn ? gitRoot : path.join(gitRoot, ".kici"))}` : pm;
58
+ for (const lockfilePath of candidates) try {
59
+ return sha256(`${prefix}\n${readFileSync(lockfilePath, "utf-8")}`);
60
+ } catch {}
61
+ return null;
57
62
  }
58
63
  /**
59
64
  * Format export reference with hash syntax.
@@ -421,13 +426,19 @@ function transformJobs(jobs, configPath, gitRoot) {
421
426
  return jobs.map((jobOrFactory, index) => {
422
427
  if (isDynamicJobFn(jobOrFactory)) {
423
428
  const groupName = getDynamicJobGroup(jobOrFactory);
429
+ const declaredNeeds = getDynamicJobNeeds(jobOrFactory);
430
+ const resolvedNeeds = declaredNeeds ? resolveNeedsForLock(declaredNeeds, uuidToName) : void 0;
424
431
  return {
425
432
  _type: "dynamic",
426
433
  source: {
427
434
  file: configPath,
428
435
  index
429
436
  },
430
- ...groupName && { group: groupName }
437
+ ...groupName && { group: groupName },
438
+ ...resolvedNeeds && {
439
+ needs: resolvedNeeds.needs,
440
+ resultAware: true
441
+ }
431
442
  };
432
443
  }
433
444
  const j = jobOrFactory;
@@ -439,40 +450,45 @@ function transformJobs(jobs, configPath, gitRoot) {
439
450
  });
440
451
  }
441
452
  /**
442
- * Normalize SDK runsOn into lock file representation.
443
- * - String passthrough: `'linux'` -> `{ runsOn: 'linux' }`
444
- * - Array passthrough: `['linux', 'docker']` -> `{ runsOn: ['linux', 'docker'] }`
445
- * - Object form: `{ labels: ['linux'], exclude: ['gpu'] }` -> `{ runsOn: ['linux'], excludeLabels: ['gpu'] }`
453
+ * Normalize SDK runsOn into lock file matchers.
454
+ * Plain strings stay exact; globs convert to regex; RegExp literals become regex.
455
+ * Throws (via the engine ReDoS gate) on a ReDoS-prone author regex.
446
456
  */
447
- function normalizeRunsOnForLock(runsOn) {
448
- if (typeof runsOn === "string") return { runsOn };
449
- if (Array.isArray(runsOn)) return { runsOn };
450
- const labels = Array.isArray(runsOn.labels) ? runsOn.labels : [runsOn.labels];
451
- const exclude = runsOn.exclude ? Array.isArray(runsOn.exclude) ? runsOn.exclude : [runsOn.exclude] : void 0;
457
+ function normalizeRunsOnForLock(runsOn, jobName) {
458
+ const { include, exclude } = normalizeRunsOnToMatchers(runsOn, `job '${jobName}' runsOn`);
452
459
  return {
453
- runsOn: labels.length === 1 ? labels[0] : labels,
454
- ...exclude && exclude.length > 0 ? { excludeLabels: exclude } : {}
460
+ runsOn: include,
461
+ ...exclude.length > 0 ? { excludeLabels: exclude } : {}
455
462
  };
456
463
  }
457
464
  /**
458
- * Validate runsOn labels:
459
- * 1. Reject kici:* reserved prefix in user-supplied labels.
460
- * 2. Reject overlap between required and excluded labels.
465
+ * Validate runsOn labels: reject overlap between required and excluded labels.
466
+ *
467
+ * `kici:` labels ARE valid runsOn selectors. runsOn expresses a requirement on
468
+ * candidate agents — it only narrows the set, it never grants authority — so
469
+ * targeting a `kici:os:linux` / `kici:role:builder` label is benign. The forgery
470
+ * boundary is label *setting* (scaler labelSet validation + the agent
471
+ * register-time scope gate), which is enforced elsewhere and unaffected here.
472
+ *
473
+ * The overlap check compares exact matchers only — a glob/regex include and an
474
+ * exact exclude (or vice versa) cannot be statically known to overlap.
461
475
  */
462
476
  function validateRunsOn(runsOn, jobName) {
463
- const labels = typeof runsOn === "string" ? [runsOn] : Array.isArray(runsOn) ? runsOn : Array.isArray(runsOn.labels) ? runsOn.labels : [runsOn.labels];
464
- const reservedLabels = labels.filter((l) => l.toLowerCase().startsWith("kici:"));
465
- if (reservedLabels.length > 0) throw new Error(`Job "${jobName}": labels with 'kici:' prefix are reserved and cannot be used in runsOn: ${reservedLabels.join(", ")}`);
466
- if (typeof runsOn === "string" || Array.isArray(runsOn)) return;
467
- const exclude = runsOn.exclude ? Array.isArray(runsOn.exclude) ? runsOn.exclude : [runsOn.exclude] : [];
468
- const overlap = labels.filter((l) => exclude.includes(l));
477
+ const { include, exclude } = normalizeRunsOnToMatchers(runsOn, `job '${jobName}' runsOn`);
478
+ const includeExact = new Set(include.filter((m) => m.kind === "exact").map((m) => m.value));
479
+ const overlap = exclude.filter((m) => m.kind === "exact").map((m) => m.value).filter((v) => includeExact.has(v));
469
480
  if (overlap.length > 0) throw new Error(`Job "${jobName}": labels and exclude overlap on [${overlap.join(", ")}]. A label cannot be both required and excluded.`);
470
481
  }
471
482
  /**
472
483
  * Transform a static job to lock file format.
473
484
  */
474
485
  function transformJob(job, configPath, index, gitRoot, uuidToName) {
475
- validateRunsOn(job.runsOn, job.name);
486
+ if (job.runsOn !== void 0 && job.runsOnAll !== void 0) throw new Error(`job '${job.name}': runsOn and runsOnAll are mutually exclusive`);
487
+ if (job.runsOn === void 0 && job.runsOnAll === void 0) throw new Error(`job '${job.name}': one of runsOn or runsOnAll is required`);
488
+ if (job.onUnreachable !== void 0 && job.runsOnAll === void 0) console.warn(`[kici] job '${job.name}': onUnreachable is ignored without runsOnAll`);
489
+ if (job.maxParallel !== void 0 && job.maxParallel < 1) throw new Error(`job '${job.name}': maxParallel must be >= 1`);
490
+ if (!(job.matrix !== void 0 || job.runsOnAll !== void 0) && (job.maxParallel !== void 0 || job.failFast !== void 0)) console.warn(`[kici] job '${job.name}': maxParallel/failFast are ignored without matrix or runsOnAll (no fan-out to bound)`);
491
+ if (job.runsOn !== void 0) validateRunsOn(job.runsOn, job.name);
476
492
  const environmentFields = {};
477
493
  if (job.environment !== void 0) {
478
494
  if (typeof job.environment === "function") {
@@ -521,7 +537,11 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
521
537
  return {
522
538
  _type: "static",
523
539
  name: job.name,
524
- ...normalizeRunsOnForLock(job.runsOn),
540
+ ...job.runsOn !== void 0 ? normalizeRunsOnForLock(job.runsOn, job.name) : {},
541
+ ...job.runsOnAll !== void 0 && { runsOnAll: normalizeRunsOnAllToMatchers(job.runsOnAll, `job '${job.name}' runsOnAll`) },
542
+ ...job.onUnreachable !== void 0 && { onUnreachable: job.onUnreachable },
543
+ ...job.maxParallel !== void 0 && { maxParallel: job.maxParallel },
544
+ ...job.failFast !== void 0 && { failFast: job.failFast },
525
545
  ...resolveNeedsForLock(job.needs, uuidToName),
526
546
  steps: transformSteps(job.steps, gitRoot),
527
547
  matrix: job.matrix ? transformMatrix(job.matrix, job.name, configPath) : void 0,
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import path from "node:path";
3
3
  import { readFileSync } from "node:fs";
4
4
  import fg from "fast-glob";
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import { normalizeLineEndings, sha256 } from "@kici-dev/core";
3
3
  //#region src/lockfile/hasher.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import { COMPILE_SCHEMA_VERSION, computeContentHash } from "./hasher.js";
3
3
  import { computeLockfileHash, detectGitRoot, generateLockFile, serializeLockFile } from "./generator.js";
4
4
  export { COMPILE_SCHEMA_VERSION, computeContentHash, computeLockfileHash, detectGitRoot, generateLockFile, serializeLockFile };
@@ -1,4 +1,4 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import ts from "typescript";
3
3
  //#region src/lockfile/purity-analyzer.ts
4
4
  /**
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-gOLHoazu.js";
2
+ import "./chunk-BTugEXQM.js";
3
3
  import path from "node:path";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { spawn } from "node:child_process";
@@ -0,0 +1,14 @@
1
+ /** Structural JWKS shape — the engine verifier owns the real `jose` type. */
2
+ export interface JsonWebKeySet {
3
+ keys: Record<string, unknown>[];
4
+ }
5
+ export interface TrustRoot {
6
+ issuer: string;
7
+ jwks: JsonWebKeySet;
8
+ }
9
+ /**
10
+ * Resolve `--trust-root`: an HTTPS issuer URL (discovery → `jwks_uri` → JWKS) or
11
+ * a self-contained offline file `{ issuer, jwks }`.
12
+ */
13
+ export declare function resolveTrustRoot(trustRoot: string): Promise<TrustRoot>;
14
+ //# sourceMappingURL=provenance-trust-root.d.ts.map
@@ -0,0 +1,49 @@
1
+ import "./chunk-BTugEXQM.js";
2
+ import { readFile } from "node:fs/promises";
3
+ //#region src/provenance-trust-root.ts
4
+ /**
5
+ * Trust-root resolution for `kici verify-attestation`.
6
+ *
7
+ * The verifier never blindly trusts a provenance token's own `iss`: the trusted
8
+ * issuer + its JWKS are supplied out-of-band via `--trust-root`. This resolver
9
+ * turns that flag into a concrete `{ issuer, jwks }`:
10
+ *
11
+ * - an **HTTPS issuer URL** (online): fetch `<url>/.well-known/openid-configuration`,
12
+ * read its `issuer` + `jwks_uri`, then fetch the JWKS;
13
+ * - a **local file path** (offline / air-gapped): a self-contained
14
+ * `{ "issuer": "...", "jwks": { "keys": [...] } }` JSON document, no network.
15
+ *
16
+ * Node-specific (`fetch` + `fs`) so the engine verify core stays pure /
17
+ * browser-safe; the JWKS is passed straight through to the engine, which owns
18
+ * the crypto, so this module defines only a structural `JsonWebKeySet` type
19
+ * rather than depending on `jose`.
20
+ */
21
+ /**
22
+ * Resolve `--trust-root`: an HTTPS issuer URL (discovery → `jwks_uri` → JWKS) or
23
+ * a self-contained offline file `{ issuer, jwks }`.
24
+ */
25
+ async function resolveTrustRoot(trustRoot) {
26
+ if (/^https?:\/\//.test(trustRoot)) {
27
+ const base = trustRoot.replace(/\/+$/, "");
28
+ const disco = await fetchJson(`${base}/.well-known/openid-configuration`);
29
+ return {
30
+ issuer: typeof disco.issuer === "string" ? disco.issuer : base,
31
+ jwks: await fetchJson(typeof disco.jwks_uri === "string" ? disco.jwks_uri : `${base}/.well-known/jwks.json`)
32
+ };
33
+ }
34
+ const file = JSON.parse(await readFile(trustRoot, "utf-8"));
35
+ if (typeof file.issuer !== "string" || !file.jwks || !Array.isArray(file.jwks.keys)) throw new Error("trust-root file must contain { issuer, jwks: { keys: [...] } }");
36
+ return {
37
+ issuer: file.issuer,
38
+ jwks: file.jwks
39
+ };
40
+ }
41
+ async function fetchJson(url) {
42
+ const res = await fetch(url);
43
+ if (!res.ok) throw new Error(`failed to fetch ${url}: ${res.status}`);
44
+ return await res.json();
45
+ }
46
+ //#endregion
47
+ export { resolveTrustRoot };
48
+
49
+ //# sourceMappingURL=provenance-trust-root.js.map
@@ -9,8 +9,6 @@ export interface GlobalConfig {
9
9
  endpoint?: string;
10
10
  /** Platform relay URL */
11
11
  platformEndpoint?: string;
12
- /** Connection mode: 'platform' or 'direct' */
13
- connectionMode?: 'platform' | 'direct';
14
12
  /** Routing key for webhook source identification (e.g., 'github:42') */
15
13
  routingKey?: string;
16
14
  /** Personal access token (from OAuth login) */
@@ -23,6 +21,12 @@ export interface GlobalConfig {
23
21
  activeOrgId?: string;
24
22
  /** User email from OIDC token */
25
23
  userEmail?: string;
24
+ /**
25
+ * Per-org default orchestrator cluster for `kici run remote`, keyed by org id.
26
+ * Set by `kici orchestrators use <cluster>`; consulted when `--orchestrator`
27
+ * is omitted so a developer with one preferred cluster types no flag.
28
+ */
29
+ defaultClusters?: Record<string, string>;
26
30
  }
27
31
  /**
28
32
  * Returns the global KiCI config directory path.
@@ -1,9 +1,8 @@
1
- import "../chunk-gOLHoazu.js";
1
+ import "../chunk-BTugEXQM.js";
2
2
  import path from "node:path";
3
3
  import fs from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  //#region src/remote/config.ts
6
- const VALID_CONNECTION_MODES = ["platform", "direct"];
7
6
  /**
8
7
  * Strips unknown keys and validates known fields from a raw config object.
9
8
  * Returns a clean GlobalConfig with only recognized, valid fields.
@@ -15,13 +14,17 @@ function sanitizeConfig(raw) {
15
14
  if (typeof obj.token === "string") config.token = obj.token;
16
15
  if (typeof obj.endpoint === "string") config.endpoint = obj.endpoint;
17
16
  if (typeof obj.platformEndpoint === "string") config.platformEndpoint = obj.platformEndpoint;
18
- if (typeof obj.connectionMode === "string" && VALID_CONNECTION_MODES.includes(obj.connectionMode)) config.connectionMode = obj.connectionMode;
19
17
  if (typeof obj.routingKey === "string") config.routingKey = obj.routingKey;
20
18
  if (typeof obj.pat === "string") config.pat = obj.pat;
21
19
  if (typeof obj.patId === "string") config.patId = obj.patId;
22
20
  if (typeof obj.patExpiresAt === "string") config.patExpiresAt = obj.patExpiresAt;
23
21
  if (typeof obj.activeOrgId === "string") config.activeOrgId = obj.activeOrgId;
24
22
  if (typeof obj.userEmail === "string") config.userEmail = obj.userEmail;
23
+ if (typeof obj.defaultClusters === "object" && obj.defaultClusters !== null && !Array.isArray(obj.defaultClusters)) {
24
+ const clusters = {};
25
+ for (const [orgId, clusterName] of Object.entries(obj.defaultClusters)) if (typeof clusterName === "string") clusters[orgId] = clusterName;
26
+ if (Object.keys(clusters).length > 0) config.defaultClusters = clusters;
27
+ }
25
28
  return config;
26
29
  }
27
30
  /**
@@ -76,13 +79,13 @@ async function mergeGlobalConfig(partial) {
76
79
  if (partial.token !== void 0) merged.token = partial.token;
77
80
  if (partial.endpoint !== void 0) merged.endpoint = partial.endpoint;
78
81
  if (partial.platformEndpoint !== void 0) merged.platformEndpoint = partial.platformEndpoint;
79
- if (partial.connectionMode !== void 0) merged.connectionMode = partial.connectionMode;
80
82
  if (partial.routingKey !== void 0) merged.routingKey = partial.routingKey;
81
83
  if (partial.pat !== void 0) merged.pat = partial.pat;
82
84
  if (partial.patId !== void 0) merged.patId = partial.patId;
83
85
  if (partial.patExpiresAt !== void 0) merged.patExpiresAt = partial.patExpiresAt;
84
86
  if (partial.activeOrgId !== void 0) merged.activeOrgId = partial.activeOrgId;
85
87
  if (partial.userEmail !== void 0) merged.userEmail = partial.userEmail;
88
+ if (partial.defaultClusters !== void 0) merged.defaultClusters = partial.defaultClusters;
86
89
  await saveGlobalConfig(merged);
87
90
  return merged;
88
91
  }