@kici-dev/compiler 0.1.22 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +20 -6
- package/dist/commands/compile.d.ts +6 -0
- package/dist/commands/compile.js +6 -3
- package/dist/commands/docs.d.ts +8 -8
- package/dist/commands/docs.js +35 -16
- package/dist/commands/org.js +2 -2
- package/dist/commands/run.d.ts +16 -1
- package/dist/commands/run.js +86 -14
- package/dist/commands/test.d.ts +4 -0
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/types.js +1 -1
- package/dist/fixtures/describe-event.d.ts +6 -0
- package/dist/fixtures/describe-event.js +18 -0
- package/dist/fixtures/picker.d.ts +19 -0
- package/dist/fixtures/picker.js +64 -0
- package/dist/llm-context/llms-architecture.txt +1440 -0
- package/dist/llm-context/llms-cli.txt +2386 -0
- package/dist/llm-context/llms-features.txt +2389 -0
- package/dist/llm-context/llms-full.txt +976 -317
- package/dist/llm-context/llms-getting-started.txt +519 -0
- package/dist/llm-context/llms-patterns.txt +1324 -0
- package/dist/llm-context/llms-providers.txt +805 -0
- package/dist/llm-context/llms-sdk.txt +3725 -0
- package/dist/llm-context/llms.txt +13 -0
- package/dist/local-executor/index.js +40 -3
- package/dist/local-executor/job-runner.d.ts +2 -0
- package/dist/local-executor/job-runner.js +36 -4
- package/dist/local-executor/types.d.ts +2 -0
- package/dist/lockfile/generator.js +13 -4
- package/dist/remote/platform-client.d.ts +6 -0
- package/dist/remote/uploader.js +1 -0
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/rule-evaluator.d.ts +1 -1
- package/dist/test-runner/rule-evaluator.js +2 -1
- package/dist/test-runner/step-context.d.ts +1 -1
- package/dist/test-runner/step-context.js +7 -2
- package/dist/types.d.ts +6 -2
- package/package.json +4 -4
- package/sbom.spdx.json +35 -35
|
@@ -12,75 +12,75 @@ Documentation for workflow authors -- people writing CI/CD pipelines in TypeScri
|
|
|
12
12
|
|
|
13
13
|
## Pages
|
|
14
14
|
|
|
15
|
-
### [Getting started with KiCI](getting-started
|
|
15
|
+
### [Getting started with KiCI](https://docs.kici.dev/user/getting-started/)
|
|
16
16
|
|
|
17
17
|
Install the SDK and compiler, write your first workflow, compile it to a lock file, and test it locally with simulated events. Covers prerequisites (Node.js 24+, pnpm), the `kici init` command for scaffolding, and the relationship between workflows, the lock file, and the three-tier runtime.
|
|
18
18
|
|
|
19
|
-
### [SDK reference](sdk-reference
|
|
19
|
+
### [SDK reference](https://docs.kici.dev/user/sdk-reference/)
|
|
20
20
|
|
|
21
21
|
Complete API reference for `@kici-dev/sdk`. Covers all factory functions (`workflow()`, `job()`, `step()`), trigger builders (`pr()`, `push()`), rule functions (`rule()`, `skip()`), matrix configuration (static arrays, static objects, dynamic functions, include/exclude), and the `StepContext` interface that steps receive at runtime.
|
|
22
22
|
|
|
23
|
-
### [Lock file and workflow drift](lock-file-and-drift
|
|
23
|
+
### [Lock file and workflow drift](https://docs.kici.dev/user/lock-file-and-drift/)
|
|
24
24
|
|
|
25
25
|
Why the lock file must stay in sync with workflow source, how to commit both together, using pre-commit and CI to catch drift early, and the agent-side hash verification when compiling from source.
|
|
26
26
|
|
|
27
|
-
### [CLI reference](cli-reference
|
|
27
|
+
### [CLI reference](https://docs.kici.dev/user/cli-reference/)
|
|
28
28
|
|
|
29
29
|
All CLI commands provided by `@kici-dev/compiler`: `kici compile` (with watch mode and check mode), `kici run` (local and remote execution), `kici test` (event simulation with dry-run, filtering, debug output, and custom payloads), `kici login`/`logout`/`org` (authentication and org management), `kici diagnostics` (infrastructure tree) and `kici runs` (`list`/`show`/`logs`/`rerun`/`cancel` — run management), `kici secrets` (secret listing), `kici types` (type generation), `kici fixture` (generate test payloads), `kici init` (interactive project scaffolding), `kici hook` (pre-commit hook installation), `kici endpoints` (webhook entrypoints), and `kici workflows` (workflow listing). Includes environment variables and exit codes.
|
|
30
30
|
|
|
31
|
-
### [Workflow patterns](workflow-patterns
|
|
31
|
+
### [Workflow patterns](https://docs.kici.dev/user/workflow-patterns/)
|
|
32
32
|
|
|
33
33
|
Common patterns for building real-world CI/CD workflows. Includes examples for basic CI pipelines with job dependencies, monorepo path-based triggering, conditional jobs with rules, matrix builds across Node versions, dynamic jobs generated at runtime, Docker-based step execution, and parallel test splitting.
|
|
34
34
|
|
|
35
|
-
### [Dashboard](dashboard
|
|
35
|
+
### [Dashboard](https://docs.kici.dev/user/dashboard/)
|
|
36
36
|
|
|
37
37
|
Guide to the KiCI web dashboard. Covers navigation (sidebar, org switcher, mobile bottom tabs), run list (table columns, filters, pagination, empty states), run detail (resizable two-panel layout, job tree, step selection, metadata tabs), log viewer (ANSI color rendering, search, permalink, copy), settings page (tabbed layout), theme toggle, keyboard shortcuts, and error pages.
|
|
38
38
|
|
|
39
|
-
### [Testing guide](testing-guide
|
|
39
|
+
### [Testing guide](https://docs.kici.dev/user/testing-guide/)
|
|
40
40
|
|
|
41
41
|
How to run and write tests for KiCI workflows, including remote test execution with `kici run remote`, fixture-based testing, and overlay mode for uncommitted changes.
|
|
42
42
|
|
|
43
|
-
### [Environments](environments
|
|
43
|
+
### [Environments](https://docs.kici.dev/user/environments/)
|
|
44
44
|
|
|
45
45
|
Configure deployment environments (staging, production, review/\*) with variables, scoped secrets, and protection rules. Covers the SDK API (`environment`, `env`, `concurrencyGroup` on jobs), the 8-layer variable merge precedence, protection rules (branch restrictions, required reviewers, wait timers, concurrency), dashboard management, type generation, and migration from the legacy contexts system.
|
|
46
46
|
|
|
47
|
-
### [Environment variables](env-vars
|
|
47
|
+
### [Environment variables](https://docs.kici.dev/user/env-vars/)
|
|
48
48
|
|
|
49
49
|
Reference for all `KICI_*` environment variables supported by the CLI. Covers authentication overrides (OIDC issuer, client ID, project ID), browser behavior (custom browser command, fixed callback port), development mode, and usage examples for CI/CD, self-hosted, and headless environments.
|
|
50
50
|
|
|
51
|
-
### [CLI authentication](cli-auth
|
|
51
|
+
### [CLI authentication](https://docs.kici.dev/user/cli-auth/)
|
|
52
52
|
|
|
53
53
|
Authenticate the KiCI CLI with browser-based OAuth (default), device authorization flow (for headless environments), or API key paste (for CI/CD pipelines). Covers org management and PATs.
|
|
54
54
|
|
|
55
|
-
### [Event system](events
|
|
55
|
+
### [Event system](https://docs.kici.dev/user/events/)
|
|
56
56
|
|
|
57
57
|
Event model concepts: event types, the registration model, event matching, and circuit breaker protection. Understanding this distinction is key to working with non-git triggers like schedules, custom events, and generic webhooks.
|
|
58
58
|
|
|
59
|
-
### [Lifecycle hooks](hooks
|
|
59
|
+
### [Lifecycle hooks](https://docs.kici.dev/user/hooks/)
|
|
60
60
|
|
|
61
61
|
SDK hook API for cancel, cleanup, success, failure, and step-level callbacks. Hooks run at specific points in the execution lifecycle to react to outcomes and perform cleanup.
|
|
62
62
|
|
|
63
|
-
### [Concurrency groups](concurrency
|
|
63
|
+
### [Concurrency groups](https://docs.kici.dev/user/concurrency/)
|
|
64
64
|
|
|
65
65
|
Control parallel execution with auto-cancel and queue modes. Prevent multiple workflow runs from executing in parallel when they target the same resource.
|
|
66
66
|
|
|
67
|
-
### [Dynamic values](dynamic-values
|
|
67
|
+
### [Dynamic values](https://docs.kici.dev/user/dynamic-values/)
|
|
68
68
|
|
|
69
69
|
Compute `environment`, `env`, and `concurrencyGroup` at runtime based on the incoming event payload. Instead of hardcoding static strings, pass a function that receives the webhook event and returns the resolved value.
|
|
70
70
|
|
|
71
|
-
### [Secrets](secrets
|
|
71
|
+
### [Secrets](https://docs.kici.dev/user/secrets/)
|
|
72
72
|
|
|
73
73
|
Access encrypted secrets in workflow steps via the explicit secrets API. Secrets are never auto-injected into `process.env` -- you must explicitly request each secret by name.
|
|
74
74
|
|
|
75
|
-
### [GitHub App provider](providers/github
|
|
75
|
+
### [GitHub App provider](https://docs.kici.dev/user/providers/github/)
|
|
76
76
|
|
|
77
77
|
The flagship source. Covers creating the GitHub App on GitHub's side (permissions, webhook URL, private key), registering it with the orchestrator via `kici-admin source add github`, routing keys (`github:<appId>`), global-workflow policy, enriched Check runs on pull requests, private-key and webhook-secret rotation, and troubleshooting.
|
|
78
78
|
|
|
79
|
-
### [Universal-git provider](providers/universal-git
|
|
79
|
+
### [Universal-git provider](https://docs.kici.dev/user/providers/universal-git/)
|
|
80
80
|
|
|
81
81
|
Connect a non-GitHub-App forge (Forgejo, Gitea, Gogs, GitLab, plain GitHub) to KiCI via its webhook. Covers preset selection, PAT and SSH credential wiring, credential rotation, global workflow policy against `generic:<orgId>:<sourceId>` routing keys, and troubleshooting.
|
|
82
82
|
|
|
83
|
-
### [Global workflows](global-workflows
|
|
83
|
+
### [Global workflows](https://docs.kici.dev/user/global-workflows/)
|
|
84
84
|
|
|
85
85
|
Cross-repo workflows that let a single workflow repo define jobs which run on events from many source repos in the same org. Covers the mental model (workflow repo vs. source repo, authoring axis vs. source axis), SDK syntax for declaring globals via `repos:` patterns, the dashboard opt-in flow and per-setting semantics (master toggle, author allow-list, source deny-list, elevated-access list), the security model, and troubleshooting skipped dispatches.
|
|
86
86
|
|
|
@@ -136,7 +136,7 @@ npx kici init --mjs
|
|
|
136
136
|
|
|
137
137
|
This creates `.mjs` workflow files that run directly without a build step.
|
|
138
138
|
|
|
139
|
-
After running `kici init`, jump straight to [Compile the workflow](
|
|
139
|
+
After running `kici init`, jump straight to [Compile the workflow](https://docs.kici.dev/user/getting-started/#compile-the-workflow) below to compile and preview the scaffolded workflow.
|
|
140
140
|
|
|
141
141
|
## Manual setup
|
|
142
142
|
|
|
@@ -215,7 +215,7 @@ This workflow:
|
|
|
215
215
|
- Runs a `lint` job first
|
|
216
216
|
- Runs a `test` job after lint succeeds (`needs: [lint]`)
|
|
217
217
|
|
|
218
|
-
`runsOn` selects which agents may run a job. Every agent self-reports `kici:os:<platform>`, `kici:arch:<cpu>`, and `kici:host:<hostname>`, so `runsOn: 'kici:os:linux'` targets any connected Linux agent with zero configuration — `kici init` scaffolds workflows with exactly that. Use a custom label such as `'linux'` or `'gpu'` (defined in your scaler's `labelSet`) to target a specific pool instead. See the [runsOn forms](
|
|
218
|
+
`runsOn` selects which agents may run a job. Every agent self-reports `kici:os:<platform>`, `kici:arch:<cpu>`, and `kici:host:<hostname>`, so `runsOn: 'kici:os:linux'` targets any connected Linux agent with zero configuration — `kici init` scaffolds workflows with exactly that. Use a custom label such as `'linux'` or `'gpu'` (defined in your scaler's `labelSet`) to target a specific pool instead. See the [runsOn forms](https://docs.kici.dev/user/sdk/core/#runson-forms) reference for the full label model.
|
|
219
219
|
|
|
220
220
|
**Single-step shortcut.** If a job only has one step, pass `run` directly to `job()` instead of building a `steps: [step(...)]` array:
|
|
221
221
|
|
|
@@ -229,7 +229,7 @@ const deploy = job('deploy', {
|
|
|
229
229
|
});
|
|
230
230
|
```
|
|
231
231
|
|
|
232
|
-
`run` and `steps` are mutually exclusive. The shorthand is ideal for deploy/notify/smoke-test jobs. See [Single-step job shorthand](sdk/core
|
|
232
|
+
`run` and `steps` are mutually exclusive. The shorthand is ideal for deploy/notify/smoke-test jobs. See [Single-step job shorthand](https://docs.kici.dev/user/sdk/core/#single-step-job-shorthand) in the SDK reference for details (output access on the resulting `job.result` is flat -- no step-name nesting).
|
|
233
233
|
|
|
234
234
|
## Compile the workflow
|
|
235
235
|
|
|
@@ -245,7 +245,7 @@ Expected output:
|
|
|
245
245
|
✓ Compiled workflows → .kici/kici.lock.json (1 workflow)
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
-
The lock file (`kici.lock.json`) is a JSON representation of your workflow that the KiCI agent uses for execution. Commit this file alongside your workflow source. See [Lock file and workflow drift](lock-file-and-drift
|
|
248
|
+
The lock file (`kici.lock.json`) is a JSON representation of your workflow that the KiCI agent uses for execution. Commit this file alongside your workflow source. See [Lock file and workflow drift](https://docs.kici.dev/user/lock-file-and-drift/) for why and how to keep them in sync.
|
|
249
249
|
|
|
250
250
|
## Preview trigger matching
|
|
251
251
|
|
|
@@ -316,7 +316,7 @@ Every `.kici/` dependency must be resolvable from the **single cloned repository
|
|
|
316
316
|
|
|
317
317
|
In practice:
|
|
318
318
|
|
|
319
|
-
- **From a registry** — the common case. Pin a published version (a private registry works — see [Private registries](
|
|
319
|
+
- **From a registry** — the common case. Pin a published version (a private registry works — see [Private registries](https://docs.kici.dev/user/private-registries/)). Available for any package manager.
|
|
320
320
|
- **From an in-repo workspace sibling** — if your `.kici/` is a member of a **pnpm workspace** or a **yarn berry workspace** (a `workspaces` array in the repo-root `package.json`), it can depend on a sibling package in the same repo via `workspace:*` (yarn berry also accepts `portal:`). The whole repo is cloned, so the sibling is present and resolves; the agent also builds your `.kici/` dependency closure after install, so a sibling's build output exists before the workflow that imports it loads. A `file:`/`link:`/`portal:` path is allowed only when it stays inside the repository.
|
|
321
321
|
|
|
322
322
|
What fails fast (with an actionable error naming the dependency, not a raw package-manager error): a `workspace:` dependency in an **npm** project (npm has no workspace protocol — pin a published version or switch to pnpm), a `workspace:`/`portal:` dependency in a **yarn classic** project (v1 has neither — use a version range, pnpm, or yarn berry), a `workspace:` dependency in a **yarn berry** project whose repo-root `package.json` has no `workspaces` array, and any `file:`/`link:`/`portal:` path that points outside the cloned repo.
|
|
@@ -450,10 +450,10 @@ The compiler watches `.kici/workflows/*.ts` and recompiles on every save.
|
|
|
450
450
|
|
|
451
451
|
## Next steps
|
|
452
452
|
|
|
453
|
-
- **[5-minute quickstart](quickstart
|
|
454
|
-
- **[SDK reference](sdk-reference
|
|
455
|
-
- **[CLI reference](cli-reference
|
|
456
|
-
- **[Workflow patterns](workflow-patterns
|
|
453
|
+
- **[5-minute quickstart](https://docs.kici.dev/user/quickstart/)** -- ready to run your workflow on real infrastructure? Stand up an orchestrator + agent (Docker / Podman or bare metal)
|
|
454
|
+
- **[SDK reference](https://docs.kici.dev/user/sdk-reference/)** -- complete API for workflows, jobs, steps, triggers, rules, and matrix
|
|
455
|
+
- **[CLI reference](https://docs.kici.dev/user/cli-reference/)** -- all CLI commands with options and examples
|
|
456
|
+
- **[Workflow patterns](https://docs.kici.dev/user/workflow-patterns/)** -- common patterns for real-world CI/CD workflows
|
|
457
457
|
|
|
458
458
|
## How KiCI works
|
|
459
459
|
|
|
@@ -475,10 +475,10 @@ The lock file approach means the orchestrator stays git-agnostic -- it only need
|
|
|
475
475
|
|
|
476
476
|
## See also
|
|
477
477
|
|
|
478
|
-
- [SDK reference](sdk-reference
|
|
479
|
-
- [CLI reference](cli-reference
|
|
480
|
-
- [Workflow patterns](workflow-patterns
|
|
481
|
-
- [Architecture overview](
|
|
478
|
+
- [SDK reference](https://docs.kici.dev/user/sdk-reference/) -- complete API for workflows, jobs, steps, triggers, rules, and matrix
|
|
479
|
+
- [CLI reference](https://docs.kici.dev/user/cli-reference/) -- all CLI commands with options and examples
|
|
480
|
+
- [Workflow patterns](https://docs.kici.dev/user/workflow-patterns/) -- common patterns for real-world CI/CD workflows
|
|
481
|
+
- [Architecture overview](https://docs.kici.dev/architecture/overview/) -- how the three-tier runtime executes your workflows
|
|
482
482
|
|
|
483
483
|
---
|
|
484
484
|
|
|
@@ -494,13 +494,13 @@ Each guide is split into two parts. **Part 1** gets you a green run against your
|
|
|
494
494
|
|
|
495
495
|
Two containers brought up with `docker compose up -d` (orchestrator + PostgreSQL), plus one short-lived agent container spawned per job by the container scaler. Minimal host setup, perfect for a laptop, home server, or a tiny VM. No need to install PostgreSQL or any other system service.
|
|
496
496
|
|
|
497
|
-
[Start with the Docker / Podman quickstart →](
|
|
497
|
+
[Start with the Docker / Podman quickstart →](https://docs.kici.dev/user/quickstart/compose/)
|
|
498
498
|
|
|
499
499
|
## Option B — Bare-metal install
|
|
500
500
|
|
|
501
501
|
Native systemd services managed by `kici-admin orchestrator install` / `kici-admin agent install` — the orchestrator and agents run as native processes. The backing PostgreSQL runs as a single container by default (one `docker compose up -d`), or you can install it natively if you'd rather not run a container runtime at all. Best for a long-lived Linux host.
|
|
502
502
|
|
|
503
|
-
[Start with the bare-metal quickstart →](
|
|
503
|
+
[Start with the bare-metal quickstart →](https://docs.kici.dev/user/quickstart/bare-metal/)
|
|
504
504
|
|
|
505
505
|
## Which should I pick?
|
|
506
506
|
|
|
@@ -516,7 +516,7 @@ If you're not sure, pick Docker / Podman.
|
|
|
516
516
|
|
|
517
517
|
## Looking for the laptop-only path?
|
|
518
518
|
|
|
519
|
-
Both quickstarts deploy a real orchestrator + agent. If you only want to write a workflow and dry-run it on your laptop with no infrastructure, [Getting started](
|
|
519
|
+
Both quickstarts deploy a real orchestrator + agent. If you only want to write a workflow and dry-run it on your laptop with no infrastructure, [Getting started](https://docs.kici.dev/user/getting-started/) covers `kici test` and `kici run local` instead.
|
|
520
520
|
|
|
521
521
|
---
|
|
522
522
|
|
|
@@ -576,7 +576,7 @@ export default workflow('ci', {
|
|
|
576
576
|
});
|
|
577
577
|
```
|
|
578
578
|
|
|
579
|
-
The `test` and `typecheck` jobs both depend on `lint`, so they run in parallel after lint succeeds. KiCI validates the dependency graph at compile time -- cycles and missing references are caught before you commit. At runtime, jobs are gated on upstream completion: a job only dispatches after every entry in its `needs` array reaches a terminal status that satisfies the edge. If an upstream fails, downstream jobs skip by default (override per-edge with `when: 'always'`). See [Job dependencies (`needs`)](
|
|
579
|
+
The `test` and `typecheck` jobs both depend on `lint`, so they run in parallel after lint succeeds. KiCI validates the dependency graph at compile time -- cycles and missing references are caught before you commit. At runtime, jobs are gated on upstream completion: a job only dispatches after every entry in its `needs` array reaches a terminal status that satisfies the edge. If an upstream fails, downstream jobs skip by default (override per-edge with `when: 'always'`). See [Job dependencies (`needs`)](https://docs.kici.dev/user/sdk/core/#job-dependencies-needs) in the SDK reference for the full matrix of `needs` forms (string, `Job` ref, `{ name, when }`, `dynamicGroup()`) and [needs-scheduler](https://docs.kici.dev/architecture/execution/needs-scheduler/) for the dispatch semantics.
|
|
580
580
|
|
|
581
581
|
**Single-step jobs don't need a `steps` array.** When a job only does one thing, pass `run` to `job()` instead of wrapping it in `steps: [step(...)]`:
|
|
582
582
|
|
|
@@ -597,7 +597,7 @@ export default workflow('smoke', {
|
|
|
597
597
|
});
|
|
598
598
|
```
|
|
599
599
|
|
|
600
|
-
`run` is mutually exclusive with `steps` (throws at compile time if both are set). Outputs are flat on `job.result` (no step-name nesting). See [Single-step job shorthand](
|
|
600
|
+
`run` is mutually exclusive with `steps` (throws at compile time if both are set). Outputs are flat on `job.result` (no step-name nesting). See [Single-step job shorthand](https://docs.kici.dev/user/sdk/core/#single-step-job-shorthand) in the SDK reference.
|
|
601
601
|
|
|
602
602
|
## PR-only workflow with branch filters
|
|
603
603
|
|
|
@@ -744,7 +744,7 @@ npx kici compile # regenerate .kici/kici.lock.json
|
|
|
744
744
|
npx kici run local dispatch
|
|
745
745
|
```
|
|
746
746
|
|
|
747
|
-
`kici run local` compiles the workflow, matches triggers against a simulated `dispatch` event, and executes the matched jobs directly on your machine with DAG-based scheduling. No webhook, no GitHub, no deployed orchestrator involved. See [`kici run local`](
|
|
747
|
+
`kici run local` compiles the workflow, matches triggers against a simulated `dispatch` event, and executes the matched jobs directly on your machine with DAG-based scheduling. No webhook, no GitHub, no deployed orchestrator involved. See [`kici run local`](https://docs.kici.dev/user/cli-reference/#kici-run-local) for options like `--job`, `--env`, `--json`, and `--junit`.
|
|
748
748
|
|
|
749
749
|
### Scoping to a single workflow
|
|
750
750
|
|
|
@@ -1267,7 +1267,7 @@ export const postDeploy = workflow('post-deploy', {
|
|
|
1267
1267
|
|
|
1268
1268
|
### Same-run discovery → fan-out
|
|
1269
1269
|
|
|
1270
|
-
A result-aware [`dynamicJob(group, { needs, generate })`](
|
|
1270
|
+
A result-aware [`dynamicJob(group, { needs, generate })`](https://docs.kici.dev/user/sdk/rules-matrix-dynamic/#dynamicjob--result-aware-generation) is deferred until its declared upstreams complete, then runs with their frozen outputs as `ctx.needs` — so a discovery job can emit a list at runtime and the generator fans out one follow-up job per item, all in the same run:
|
|
1271
1271
|
|
|
1272
1272
|
```typescript
|
|
1273
1273
|
import { workflow, job, step, push, dynamicJob, z } from '@kici-dev/sdk';
|
|
@@ -1373,7 +1373,7 @@ export default workflow('on-argocd-deploy', {
|
|
|
1373
1373
|
});
|
|
1374
1374
|
```
|
|
1375
1375
|
|
|
1376
|
-
See the [Operator guide: event routing](
|
|
1376
|
+
See the [Operator guide: event routing](https://docs.kici.dev/operator/event-routing/) for how to set up generic webhook sources, verification methods, and trust relationships.
|
|
1377
1377
|
|
|
1378
1378
|
## Stripe webhook handler
|
|
1379
1379
|
|
|
@@ -1415,9 +1415,9 @@ export default workflow('stripe-invoice-handler', {
|
|
|
1415
1415
|
|
|
1416
1416
|
**Prerequisites:**
|
|
1417
1417
|
|
|
1418
|
-
- An operator must create a generic webhook source named `stripe` via the admin API. See [Operator guide: creating a source](
|
|
1418
|
+
- An operator must create a generic webhook source named `stripe` via the admin API. See [Operator guide: creating a source](https://docs.kici.dev/operator/event-routing/#creating-a-source).
|
|
1419
1419
|
- The `stripe-signing-key` secret must contain your Stripe webhook signing secret.
|
|
1420
|
-
- This workflow uses the [registration model](
|
|
1420
|
+
- This workflow uses the [registration model](https://docs.kici.dev/user/events/#the-registration-model) -- it will not trigger until you push to your default branch.
|
|
1421
1421
|
|
|
1422
1422
|
## Self-hosted git forge (Gogs, Forgejo, Gitea)
|
|
1423
1423
|
|
|
@@ -1466,7 +1466,7 @@ export default workflow('on-forgejo-push', {
|
|
|
1466
1466
|
|
|
1467
1467
|
**Caveat — cloning:** generic webhook sources deliver only the payload; they do not carry a clone token, and KiCI's automatic pre-step clone (`packages/agent/src/checkout/git-clone.ts`) is GitHub-only today (HTTPS + `http.extraHeader` Basic auth with a GitHub installation token). Three practical patterns:
|
|
1468
1468
|
|
|
1469
|
-
- **Mirror to GitHub and fan out.** Keep the repo on GitHub, register the workflow via a GitHub default-branch push, and have Gogs/Forgejo webhooks fan out via [cross-source delivery](
|
|
1469
|
+
- **Mirror to GitHub and fan out.** Keep the repo on GitHub, register the workflow via a GitHub default-branch push, and have Gogs/Forgejo webhooks fan out via [cross-source delivery](https://docs.kici.dev/architecture/webhooks/webhook-delivery/#cross-source-delivery). The clone runs against the GitHub mirror using the GitHub App's token.
|
|
1470
1470
|
- **Clone yourself using a secret.** Set `checkout: false` on the job to skip the framework clone, store an SSH private key or forge personal access token as a secret, and run `git clone` explicitly in the first step. This works for any forge the agent can reach, no mirror needed. You still need a way to **register** the workflow — either keep a one-file GitHub repo whose only job is to own the registration, or bootstrap the registration manually against the orchestrator DB.
|
|
1471
1471
|
- **Self-contained workflow.** No clone at all. The step reads whatever it needs from `rawPayload` (e.g., `rawPayload.after`, `rawPayload.repository.clone_url`) and drives external systems — notifications, deploys, third-party CI triggers.
|
|
1472
1472
|
|
|
@@ -1500,7 +1500,7 @@ HTTPS with a forge PAT works the same way — store the token as a secret, `awai
|
|
|
1500
1500
|
|
|
1501
1501
|
- An operator must create a generic webhook source via `kici-admin source add generic` (see above).
|
|
1502
1502
|
- The forge's webhook secret must match the `--secret` value.
|
|
1503
|
-
- The workflow uses the [registration model](
|
|
1503
|
+
- The workflow uses the [registration model](https://docs.kici.dev/user/events/#the-registration-model) -- push to the default branch of a registered repo before the first webhook fires.
|
|
1504
1504
|
|
|
1505
1505
|
## Plain GitHub repo webhooks (no GitHub App)
|
|
1506
1506
|
|
|
@@ -1562,12 +1562,12 @@ export default workflow('on-github-repo-push', {
|
|
|
1562
1562
|
**What you lose compared to the GitHub App** (these are the same cloning / metadata caveats that apply to the Gogs/Forgejo pattern, plus GitHub-specific integrations):
|
|
1563
1563
|
|
|
1564
1564
|
- No auto-clone — `packages/agent/src/checkout/git-clone.ts` uses GitHub App installation tokens to fetch the repo; a generic source has none. Either set `checkout: false` and clone yourself with a PAT/Deploy Key secret (same pattern as the Forgejo manual-clone example above), or keep the workflow self-contained.
|
|
1565
|
-
- No lock-file fetch — the orchestrator cannot fetch `.kici/kici.lock.json` at the pushed SHA via the GitHub API. The workflow must be pre-registered via the [registration model](
|
|
1565
|
+
- No lock-file fetch — the orchestrator cannot fetch `.kici/kici.lock.json` at the pushed SHA via the GitHub API. The workflow must be pre-registered via the [registration model](https://docs.kici.dev/user/events/#the-registration-model); ad-hoc per-commit workflow discovery that a GitHub App push gives you is not available.
|
|
1566
1566
|
- No changed-files enrichment — `event.changedFiles` is empty. Use JSONPath `match` on `rawPayload.commits[*].added/modified/removed` if you need path filters.
|
|
1567
1567
|
- No check-run integration — KiCI cannot post Check Run results back to GitHub.
|
|
1568
1568
|
- Workflow authors must use `genericWebhook()`, not `push()` / `pr()` / `webhook()` — the latter three only match events delivered through the native GitHub App provider.
|
|
1569
1569
|
|
|
1570
|
-
**When to use it anyway:** trigger-only workflows that don't need the cloned repo — posting Slack messages, kicking off external deploys, forwarding to downstream systems, or exposing GitHub repo events as `genericWebhook` for same-org [cross-source fan-out](
|
|
1570
|
+
**When to use it anyway:** trigger-only workflows that don't need the cloned repo — posting Slack messages, kicking off external deploys, forwarding to downstream systems, or exposing GitHub repo events as `genericWebhook` for same-org [cross-source fan-out](https://docs.kici.dev/architecture/webhooks/webhook-delivery/#cross-source-delivery). For anything that compiles, tests, or checks code, install the GitHub App instead.
|
|
1571
1571
|
|
|
1572
1572
|
## Nightly cron build
|
|
1573
1573
|
|
|
@@ -1700,7 +1700,7 @@ export default workflow('nightly-build', {
|
|
|
1700
1700
|
**Notes:**
|
|
1701
1701
|
|
|
1702
1702
|
- The `cron` field uses standard 5-field cron syntax. Use the `timezone` option (defaults to `'UTC'`) to control evaluation in a specific timezone: `schedule({ cron: '0 2 * * *', timezone: 'America/New_York' })`.
|
|
1703
|
-
- Schedule workflows use the [registration model](
|
|
1703
|
+
- Schedule workflows use the [registration model](https://docs.kici.dev/user/events/#the-registration-model) -- the cron will not start firing until you push to your default branch.
|
|
1704
1704
|
- In clustered orchestrator deployments, only the Raft leader evaluates cron schedules. If the leader changes, the new leader recovers missed schedules.
|
|
1705
1705
|
|
|
1706
1706
|
**Timing precision and scaling:**
|
|
@@ -1764,7 +1764,7 @@ export const deploy = workflow('deploy-on-success', {
|
|
|
1764
1764
|
|
|
1765
1765
|
- `workflowComplete()` is a system event trigger -- the orchestrator automatically emits these events when workflows finish. You do not need to call `ctx.emit()`.
|
|
1766
1766
|
- The `status` filter accepts `'success'`, `'failed'`, and `'cancelled'`. Omit `status` to trigger on any completion.
|
|
1767
|
-
- The `deploy-on-success` workflow uses the [registration model](
|
|
1767
|
+
- The `deploy-on-success` workflow uses the [registration model](https://docs.kici.dev/user/events/#the-registration-model) -- it will not trigger until you push to your default branch. The `build` workflow (using `push()`) works immediately.
|
|
1768
1768
|
- You can also use `jobComplete()` to trigger on individual job completions within a workflow.
|
|
1769
1769
|
|
|
1770
1770
|
## Custom event chaining
|
|
@@ -1836,8 +1836,8 @@ export const autoDeploy = workflow('auto-deploy', {
|
|
|
1836
1836
|
- `defineEvent()` creates a typed contract using Zod. This is optional but recommended for documenting event payloads.
|
|
1837
1837
|
- Custom events are delivered immediately when `ctx.emit()` is called (mid-workflow), not queued until the workflow completes.
|
|
1838
1838
|
- Payload matching is available via the `match` option: `kiciEvent({ name: 'tests-passed', match: { '$.branch': 'main' } })`.
|
|
1839
|
-
- The `auto-deploy` workflow uses the [registration model](
|
|
1840
|
-
- The [circuit breaker](
|
|
1839
|
+
- The `auto-deploy` workflow uses the [registration model](https://docs.kici.dev/user/events/#the-registration-model) -- it will not trigger until you push to your default branch.
|
|
1840
|
+
- The [circuit breaker](https://docs.kici.dev/user/events/#circuit-breaker) limits chain depth (default: 10) and rate (default: 100/min per workflow) to prevent infinite loops.
|
|
1841
1841
|
|
|
1842
1842
|
## Step context
|
|
1843
1843
|
|
|
@@ -1856,7 +1856,7 @@ Two surfaces drive the same cache:
|
|
|
1856
1856
|
- **Declarative** — a `cache` field on a job or a step. The runtime restores before the work runs and saves after it succeeds, with no code in your step body.
|
|
1857
1857
|
- **Imperative** — `ctx.cache.restore(spec)` / `ctx.cache.save(spec)` inside a step body, for fine-grained control over when restore and save happen.
|
|
1858
1858
|
|
|
1859
|
-
The cache is backed by the orchestrator's object storage. Entries are isolated per organization and per ref scope (see [Isolation](
|
|
1859
|
+
The cache is backed by the orchestrator's object storage. Entries are isolated per organization and per ref scope (see [Isolation](https://docs.kici.dev/user/sdk/caching/#isolation)); no other tenant can read your cache, and an untrusted/fork ref can never poison the cache a trusted branch reads.
|
|
1860
1860
|
|
|
1861
1861
|
## CacheSpec
|
|
1862
1862
|
|
|
@@ -1973,18 +1973,18 @@ Cache storage is bounded per organization. Two mechanisms keep it bounded:
|
|
|
1973
1973
|
- **Quota** — when a save pushes the org over its byte quota (`KICI_USER_CACHE_QUOTA_BYTES`, default 5 GiB), the oldest entries are evicted until the org is back under quota.
|
|
1974
1974
|
- **TTL** — entries unused for `KICI_USER_CACHE_TTL_MS` (default 7 days) expire. The TTL refreshes on read (touch-on-read), so an actively used cache stays warm.
|
|
1975
1975
|
|
|
1976
|
-
Both knobs are operator-configured on the orchestrator — see [orchestrator storage layout](
|
|
1976
|
+
Both knobs are operator-configured on the orchestrator — see [orchestrator storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/).
|
|
1977
1977
|
|
|
1978
1978
|
## Observability
|
|
1979
1979
|
|
|
1980
|
-
Each cache restore and save surfaces in the run timeline as a `cache:restore` / `cache:save` pseudo-step, reporting the outcome (hit/miss/saved, the matched key, bytes). The same outcomes are recorded as `cache.restore` / `cache.save` run events. See [data flows](
|
|
1980
|
+
Each cache restore and save surfaces in the run timeline as a `cache:restore` / `cache:save` pseudo-step, reporting the outcome (hit/miss/saved, the matched key, bytes). The same outcomes are recorded as `cache.restore` / `cache.save` run events. See [data flows](https://docs.kici.dev/architecture/data-flows/#user-facing-cache-flow) for the restore/save protocol.
|
|
1981
1981
|
|
|
1982
1982
|
## See also
|
|
1983
1983
|
|
|
1984
|
-
- [Core](
|
|
1985
|
-
- [Runtime](
|
|
1986
|
-
- [Orchestrator storage layout](
|
|
1987
|
-
- [Data flows](
|
|
1984
|
+
- [Core](https://docs.kici.dev/user/sdk/core/) -- `job()` / `step()` factories the `cache` field attaches to
|
|
1985
|
+
- [Runtime](https://docs.kici.dev/user/sdk/runtime/) -- `StepContext`, where `ctx.cache` lives
|
|
1986
|
+
- [Orchestrator storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/) -- cache prefix, quota, TTL, and eviction
|
|
1987
|
+
- [Data flows](https://docs.kici.dev/architecture/data-flows/#user-facing-cache-flow) -- restore/save protocol and trust→scope mapping
|
|
1988
1988
|
|
|
1989
1989
|
---
|
|
1990
1990
|
|
|
@@ -2018,8 +2018,8 @@ function workflow(name: string, options: WorkflowOptions): Workflow;
|
|
|
2018
2018
|
| `options.cleanup` | `HookInput` | no | Always runs after the workflow (success, failure, or cancel) |
|
|
2019
2019
|
| `options.onSuccess` | `HookInput` | no | Runs on workflow success |
|
|
2020
2020
|
| `options.onFailure` | `HookInput` | no | Runs on workflow failure |
|
|
2021
|
-
| `options.concurrency` | `{ group: (ctx) => string; cancelInProgress?: boolean; max?: number }` | no | Workflow-scoped concurrency. See [Concurrency](
|
|
2022
|
-
| `options.timeout` | `number` | no | Whole-run wall-clock timeout in milliseconds across all jobs. On breach the orchestrator cancels the run and marks it timed out. See [Timeouts](
|
|
2021
|
+
| `options.concurrency` | `{ group: (ctx) => string; cancelInProgress?: boolean; max?: number }` | no | Workflow-scoped concurrency. See [Concurrency](https://docs.kici.dev/user/concurrency/). |
|
|
2022
|
+
| `options.timeout` | `number` | no | Whole-run wall-clock timeout in milliseconds across all jobs. On breach the orchestrator cancels the run and marks it timed out. See [Timeouts](https://docs.kici.dev/user/sdk/core/#timeouts). |
|
|
2023
2023
|
|
|
2024
2024
|
**Returns:** `Workflow` -- an immutable workflow definition.
|
|
2025
2025
|
|
|
@@ -2032,7 +2032,7 @@ export default workflow('ci', {
|
|
|
2032
2032
|
});
|
|
2033
2033
|
```
|
|
2034
2034
|
|
|
2035
|
-
Secret scoping happens at the job level via `environment` (see [job options](
|
|
2035
|
+
Secret scoping happens at the job level via `environment` (see [job options](https://docs.kici.dev/user/sdk/core/#jobname-options--joboptions) and [Secrets](https://docs.kici.dev/user/secrets/)) — the workflow itself does not declare which secret environments it can read.
|
|
2036
2036
|
|
|
2037
2037
|
### job(name, options) / job(options)
|
|
2038
2038
|
|
|
@@ -2050,8 +2050,8 @@ function job(options: JobOptions): Job;
|
|
|
2050
2050
|
| `name` | `string` | no | Job name (auto-generated UUID if omitted) |
|
|
2051
2051
|
| `options.runsOn` | `RunsOn` | yes | Runner label(s) and optional exclusions (see below) |
|
|
2052
2052
|
| `options.steps` | `StepInput[]` | yes (or use `run`) | Steps to execute in order. Mutually exclusive with `run`. |
|
|
2053
|
-
| `options.run` | `(ctx) => Promise<unknown>` | yes (or use `steps`) | Single-step shorthand -- see [Single-step job shorthand](
|
|
2054
|
-
| `options.needs` | `NeedsEntry[]` | no | Job dependencies (must complete first) -- see [Job dependencies (`needs`)](
|
|
2053
|
+
| `options.run` | `(ctx) => Promise<unknown>` | yes (or use `steps`) | Single-step shorthand -- see [Single-step job shorthand](https://docs.kici.dev/user/sdk/core/#single-step-job-shorthand). Mutually exclusive with `steps`. |
|
|
2054
|
+
| `options.needs` | `NeedsEntry[]` | no | Job dependencies (must complete first) -- see [Job dependencies (`needs`)](https://docs.kici.dev/user/sdk/core/#job-dependencies-needs) |
|
|
2055
2055
|
| `options.rules` | `Rule[]` | no | Conditions for conditional execution |
|
|
2056
2056
|
| `options.description` | `string` | no | Human-readable description |
|
|
2057
2057
|
| `options.matrix` | `Matrix` | no | Matrix configuration for job expansion |
|
|
@@ -2059,19 +2059,19 @@ function job(options: JobOptions): Job;
|
|
|
2059
2059
|
| `options.exclude` | `MatrixExclude[]` | no | Matrix combinations to remove |
|
|
2060
2060
|
| `options.checkout` | `boolean` | no (default: `true`) | When `false`, agent skips git clone. Useful for deploy/notify jobs. |
|
|
2061
2061
|
| `options.container` | `string \| ContainerConfig` | no | Docker image for job execution. String form is the image name; object form adds `env`. All steps run inside the container. |
|
|
2062
|
-
| `options.environment` | `string \| ((event) => string \| Promise<string>)` | no | Deployment environment for this job. Static string or async/dynamic function -- see [Dynamic values](
|
|
2063
|
-
| `options.env` | `Record<string, string> \| ((event) => Record<string, string>)` | no | Environment variables. Static object or async/dynamic function -- see [Dynamic values](
|
|
2064
|
-
| `options.concurrencyGroup` | `string \| ((event) => string \| Promise<string>)` | no | Concurrency group name (defaults to environment name) -- see [Concurrency](
|
|
2062
|
+
| `options.environment` | `string \| ((event) => string \| Promise<string>)` | no | Deployment environment for this job. Static string or async/dynamic function -- see [Dynamic values](https://docs.kici.dev/user/dynamic-values/). |
|
|
2063
|
+
| `options.env` | `Record<string, string> \| ((event) => Record<string, string>)` | no | Environment variables. Static object or async/dynamic function -- see [Dynamic values](https://docs.kici.dev/user/dynamic-values/). |
|
|
2064
|
+
| `options.concurrencyGroup` | `string \| ((event) => string \| Promise<string>)` | no | Concurrency group name (defaults to environment name) -- see [Concurrency](https://docs.kici.dev/user/concurrency/). |
|
|
2065
2065
|
| `options.onCancel` | `HookInput` | no | Hook that runs when the job is cancelled |
|
|
2066
2066
|
| `options.cleanup` | `HookInput` | no | Hook that always runs after completion |
|
|
2067
2067
|
| `options.onSuccess` | `HookInput` | no | Hook that runs when the job succeeds |
|
|
2068
2068
|
| `options.onFailure` | `HookInput` | no | Hook that runs when the job fails |
|
|
2069
2069
|
| `options.beforeStep` | `HookInput` | no | Hook that runs before each step |
|
|
2070
2070
|
| `options.afterStep` | `HookInput` | no | Hook that runs after each step |
|
|
2071
|
-
| `options.gracePeriod` | `number` | no | Seconds before SIGKILL after SIGTERM during cancellation -- see [Hooks](
|
|
2072
|
-
| `options.timeout` | `number` | no | Total job wall-clock timeout in milliseconds (init + all steps + hooks). On breach the job is aborted and reported timed out. See [Timeouts](
|
|
2073
|
-
| `options.resources` | `ResourceRequest` | no | Per-job CPU / memory request and limit. See [Per-job resources](
|
|
2074
|
-
| `options.init` | `InitConfig` | no | Per-job initialization run after clone, before steps -- provisions a toolchain. A generic config, a typed preset (`'mise'` / `{ mise }`), `'auto'`, or `false`. See [Per-job init](
|
|
2071
|
+
| `options.gracePeriod` | `number` | no | Seconds before SIGKILL after SIGTERM during cancellation -- see [Hooks](https://docs.kici.dev/user/hooks/#hook-timeout). |
|
|
2072
|
+
| `options.timeout` | `number` | no | Total job wall-clock timeout in milliseconds (init + all steps + hooks). On breach the job is aborted and reported timed out. See [Timeouts](https://docs.kici.dev/user/sdk/core/#timeouts). |
|
|
2073
|
+
| `options.resources` | `ResourceRequest` | no | Per-job CPU / memory request and limit. See [Per-job resources](https://docs.kici.dev/user/sdk/core/#per-job-resources) below. |
|
|
2074
|
+
| `options.init` | `InitConfig` | no | Per-job initialization run after clone, before steps -- provisions a toolchain. A generic config, a typed preset (`'mise'` / `{ mise }`), `'auto'`, or `false`. See [Per-job init](https://docs.kici.dev/user/sdk/core/#per-job-init) below. |
|
|
2075
2075
|
|
|
2076
2076
|
**Returns:** `Job` -- an immutable job definition.
|
|
2077
2077
|
|
|
@@ -2092,7 +2092,7 @@ const build = job({
|
|
|
2092
2092
|
|
|
2093
2093
|
#### runsOn forms
|
|
2094
2094
|
|
|
2095
|
-
A job's `runsOn` selects which agents may run it. Every label listed must be present on the agent (a subset match). It accepts three forms, and each label can be an exact string, a glob, or a regular expression (see [Targeting by pattern](
|
|
2095
|
+
A job's `runsOn` selects which agents may run it. Every label listed must be present on the agent (a subset match). It accepts three forms, and each label can be an exact string, a glob, or a regular expression (see [Targeting by pattern](https://docs.kici.dev/user/sdk/core/#targeting-by-pattern) below):
|
|
2096
2096
|
|
|
2097
2097
|
```typescript
|
|
2098
2098
|
// 1. Simple string -- agent must have this label
|
|
@@ -2118,7 +2118,7 @@ runsOn: { labels: ['kici:os:linux'], exclude: ['kici:host:box-01'] }
|
|
|
2118
2118
|
- **Required labels:** The agent must have every label in the `labels` array (or the string/array form).
|
|
2119
2119
|
- **Excluded labels:** The agent must NOT have any label in the `exclude` array. This includes auto-derived labels like `kici:arch:arm64`, `kici:os:linux`, etc.
|
|
2120
2120
|
- **Compile-time validation:** The compiler will error if any label appears in both `labels` and `exclude` (overlap detection).
|
|
2121
|
-
- **Operator-declared mandatory labels:** Operators may mark a scaler with `mandatoryLabels` (Kubernetes-taint-style opt-in). When a scaler declares a mandatory label, a job is only allowed to land on it if `runsOn.labels` includes that label. A workflow targeting such a scaler must explicitly list the mandatory label in `runsOn`. See the [auto-scaler mandatory labels](
|
|
2121
|
+
- **Operator-declared mandatory labels:** Operators may mark a scaler with `mandatoryLabels` (Kubernetes-taint-style opt-in). When a scaler declares a mandatory label, a job is only allowed to land on it if `runsOn.labels` includes that label. A workflow targeting such a scaler must explicitly list the mandatory label in `runsOn`. See the [auto-scaler mandatory labels](https://docs.kici.dev/operator/orchestrator/auto-scaler/common-config/#mandatory--exclude-labels) for details.
|
|
2122
2122
|
|
|
2123
2123
|
```typescript
|
|
2124
2124
|
// Route to any Linux agent that does NOT have the 'gpu' label
|
|
@@ -2134,6 +2134,33 @@ const deploy = job('deploy', {
|
|
|
2134
2134
|
});
|
|
2135
2135
|
```
|
|
2136
2136
|
|
|
2137
|
+
#### Single-host selection: `pick`
|
|
2138
|
+
|
|
2139
|
+
When more than one agent matches a `runsOn` selector, the object form's `pick`
|
|
2140
|
+
field controls **which** one runs the job:
|
|
2141
|
+
|
|
2142
|
+
```typescript
|
|
2143
|
+
// Always the same host across re-runs (default — can be omitted)
|
|
2144
|
+
runsOn: { labels: ['role:db'], pick: 'deterministic' }
|
|
2145
|
+
|
|
2146
|
+
// Any available host (load spread)
|
|
2147
|
+
runsOn: { labels: ['role:db'], pick: 'any' }
|
|
2148
|
+
```
|
|
2149
|
+
|
|
2150
|
+
- **`'deterministic'` (the default)** — the orchestrator sorts the matching
|
|
2151
|
+
agents by their agent id and picks the lowest. A job that must run exactly
|
|
2152
|
+
once on one stable host — a database migration, a backup dump — lands on the
|
|
2153
|
+
**same** host every run. The string and array shorthand forms
|
|
2154
|
+
(`runsOn: 'role:db'`, `runsOn: ['role:db', 'linux']`) inherit this default.
|
|
2155
|
+
- **`'any'`** — pick any available matching agent. Use this for jobs that don't
|
|
2156
|
+
need a stable host and benefit from spreading load across an equivalent pool.
|
|
2157
|
+
|
|
2158
|
+
**Trade-off:** `'deterministic'` can hot-spot — if many jobs target the same
|
|
2159
|
+
label set, they all pin to the same lowest-id agent. Use `'any'` to spread those
|
|
2160
|
+
across the pool; keep `'deterministic'` when reproducibility matters more than
|
|
2161
|
+
balance. (`pick` selects among **single-agent** candidates; to fan a job out to
|
|
2162
|
+
**every** matching host, use [`runsOnAll`](https://docs.kici.dev/user/sdk/runs-on-all/) instead.)
|
|
2163
|
+
|
|
2137
2164
|
#### Targeting by pattern
|
|
2138
2165
|
|
|
2139
2166
|
Every selector element — in `runsOn`, in `runsOnAll`, on both the include and the exclude side — can be a plain string, a glob pattern, or a regular expression. KiCI picks the matching mode from the value itself:
|
|
@@ -2238,7 +2265,7 @@ const configureNginx = step('configure-nginx', {
|
|
|
2238
2265
|
```
|
|
2239
2266
|
|
|
2240
2267
|
A checked step can run in apply mode (converge) or `--check` preview mode (report
|
|
2241
|
-
drift, change nothing). See [Idempotent steps and check mode](
|
|
2268
|
+
drift, change nothing). See [Idempotent steps and check mode](https://docs.kici.dev/user/idempotent-steps/).
|
|
2242
2269
|
|
|
2243
2270
|
### Per-job resources
|
|
2244
2271
|
|
|
@@ -2321,7 +2348,7 @@ export const build = workflow('build', {
|
|
|
2321
2348
|
| --------- | ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2322
2349
|
| `run` | `string` | yes | Command run after clone, before steps. Runs in the job's sandbox at the clone root. Must be a non-empty command. |
|
|
2323
2350
|
| `shell` | `string` | no | Shell used to run `run`. Defaults to `bash`. |
|
|
2324
|
-
| `cache` | `CacheSpec` | no | Cache spec for binaries the command installs -- restored before the command, saved after on a key miss. See [Caching](
|
|
2351
|
+
| `cache` | `CacheSpec` | no | Cache spec for binaries the command installs -- restored before the command, saved after on a key miss. See [Caching](https://docs.kici.dev/user/sdk/caching/). |
|
|
2325
2352
|
| `timeout` | `number` | no | Max wall-clock for this init command in milliseconds. Defaults to 10 minutes. On breach the init is aborted and the job is reported timed out. |
|
|
2326
2353
|
| `env` | `Record<string,string>` | no | Static environment variables available to the command. |
|
|
2327
2354
|
|
|
@@ -2504,6 +2531,43 @@ export default workflow('ci', {
|
|
|
2504
2531
|
|
|
2505
2532
|
Workflow and job timeouts surface with a distinct "timed out" reason so the dashboard labels the run or job as timed out rather than a generic failure or cancel.
|
|
2506
2533
|
|
|
2534
|
+
### Retries
|
|
2535
|
+
|
|
2536
|
+
A step can declare a `retry` policy so a thrown attempt is re-run automatically instead of failing the job on the first error. Use it for genuinely transient failures — a flaky network call, an occasional 503, a dependency that is briefly not ready.
|
|
2537
|
+
|
|
2538
|
+
```typescript
|
|
2539
|
+
step('publish', {
|
|
2540
|
+
retry: 3, // shorthand for { maxAttempts: 3 } with the defaults below
|
|
2541
|
+
run: async (ctx) => {
|
|
2542
|
+
await ctx.$`pnpm publish`;
|
|
2543
|
+
},
|
|
2544
|
+
});
|
|
2545
|
+
|
|
2546
|
+
step('fetch-token', {
|
|
2547
|
+
retry: {
|
|
2548
|
+
maxAttempts: 5, // total attempts including the first; must be >= 1
|
|
2549
|
+
delayMs: 500, // base delay between attempts (default 1000)
|
|
2550
|
+
backoff: 'exponential', // 'exponential' (default) or 'fixed'
|
|
2551
|
+
maxDelayMs: 30_000, // cap for exponential growth (default 30000)
|
|
2552
|
+
retryIf: (err) => err instanceof TransientError, // default: retry on any throw
|
|
2553
|
+
},
|
|
2554
|
+
run: async (ctx) => {
|
|
2555
|
+
await fetchToken();
|
|
2556
|
+
},
|
|
2557
|
+
});
|
|
2558
|
+
```
|
|
2559
|
+
|
|
2560
|
+
- **`retry: N`** is shorthand for `{ maxAttempts: N }` with all defaults applied.
|
|
2561
|
+
- **Defaults:** `delayMs: 1000`, `backoff: 'exponential'`, `maxDelayMs: 30000`, and "retry on any throw" when no `retryIf` is given.
|
|
2562
|
+
- **Backoff.** With `'exponential'`, the wait after the `n`-th attempt (1-based) is `min(delayMs * 2 ** (n - 1), maxDelayMs)` — 1s, 2s, 4s, … capped at `maxDelayMs`. With `'fixed'`, the wait is always `delayMs`.
|
|
2563
|
+
- **`retryIf(err)`** runs against the thrown error before each retry; return `false` to stop retrying immediately and let the failure stand.
|
|
2564
|
+
- **Timeout is per-attempt.** Each attempt gets the step's full `timeout` budget — a timed-out attempt counts as one failed attempt and is retried while attempts remain. The total wall-clock can therefore approach `maxAttempts * (timeout + delay)`, so keep `maxAttempts` and `maxDelayMs` sane (the job-level `timeout` still bounds the whole job).
|
|
2565
|
+
- **Retries exhaust before `continueOnError`.** A step with both retries first; only the _final_ failure is then softened to a warning by `continueOnError`.
|
|
2566
|
+
|
|
2567
|
+
`retry` works identically under `kici run local` and on a remote agent, and applies to dynamically-generated job steps too. The `retryIf` predicate is an in-memory function: it is honored at execution time but never serialized into the lock file.
|
|
2568
|
+
|
|
2569
|
+
> **Retry vs. wait-until-condition.** `retry` re-runs a step that _throws_. To poll until a condition becomes true (a port listening, a `/health` endpoint returning 200, a unit becoming active), use [`waitForStep`](https://docs.kici.dev/user/sdk/wait-for/) instead — it is purpose-built for declarative wait-for-condition with intervals, a timeout, and on-timeout handling.
|
|
2570
|
+
|
|
2507
2571
|
### Output chaining
|
|
2508
2572
|
|
|
2509
2573
|
Steps and jobs can access outputs from preceding steps/jobs using two patterns.
|
|
@@ -2586,7 +2650,7 @@ const deploy = job('deploy', {
|
|
|
2586
2650
|
|
|
2587
2651
|
**Important:** `needs` must be declared explicitly. Output chaining does not auto-infer dependencies -- you must list job dependencies in `needs` even if you access their outputs via `.result`.
|
|
2588
2652
|
|
|
2589
|
-
Cross-job output chaining works in both local test mode (`kici test`) and remote pipeline execution. The orchestrator's needs-aware dispatch scheduler guarantees upstream jobs reach a terminal state before downstream jobs dispatch, and upstream outputs are transported to the downstream agent sandbox via the `upstreamJobOutputs` field on `job.dispatch`. See [needs-scheduler](
|
|
2653
|
+
Cross-job output chaining works in both local test mode (`kici test`) and remote pipeline execution. The orchestrator's needs-aware dispatch scheduler guarantees upstream jobs reach a terminal state before downstream jobs dispatch, and upstream outputs are transported to the downstream agent sandbox via the `upstreamJobOutputs` field on `job.dispatch`. See [needs-scheduler](https://docs.kici.dev/architecture/execution/needs-scheduler/) for the full dispatch semantics.
|
|
2590
2654
|
|
|
2591
2655
|
### Job dependencies (`needs`)
|
|
2592
2656
|
|
|
@@ -2641,7 +2705,7 @@ job('report', {
|
|
|
2641
2705
|
});
|
|
2642
2706
|
```
|
|
2643
2707
|
|
|
2644
|
-
For an arbitrary outcome-based gate that prevents a job from dispatching at all, use a result-aware `dynamicJob` that returns `[]` or `[job]` based on `ctx.needs.<job>.status` — see [Dynamic jobs](
|
|
2708
|
+
For an arbitrary outcome-based gate that prevents a job from dispatching at all, use a result-aware `dynamicJob` that returns `[]` or `[job]` based on `ctx.needs.<job>.status` — see [Dynamic jobs](https://docs.kici.dev/architecture/execution/dynamic-jobs/).
|
|
2645
2709
|
|
|
2646
2710
|
**DAG validation:** three-layer cycle detection.
|
|
2647
2711
|
|
|
@@ -2686,7 +2750,7 @@ Tag a dynamic job generator function with a group name so other jobs can referen
|
|
|
2686
2750
|
function dynamicJob(groupName: string, fn: DynamicJobFn): DynamicJobFn;
|
|
2687
2751
|
```
|
|
2688
2752
|
|
|
2689
|
-
The generator runs twice: once in the init phase (to register expected job names) and once inside the executing agent (to produce the actual jobs). Mismatches between the two evaluations are detected as determinism drift -- see [dynamic-jobs](
|
|
2753
|
+
The generator runs twice: once in the init phase (to register expected job names) and once inside the executing agent (to produce the actual jobs). Mismatches between the two evaluations are detected as determinism drift -- see [dynamic-jobs](https://docs.kici.dev/architecture/execution/dynamic-jobs/).
|
|
2690
2754
|
|
|
2691
2755
|
### Auto-generated IDs
|
|
2692
2756
|
|
|
@@ -3051,13 +3115,13 @@ The typed `payload` shapes above reference these partial GitHub object types. Ea
|
|
|
3051
3115
|
|
|
3052
3116
|
Source: https://docs.kici.dev/user/sdk/idempotent/
|
|
3053
3117
|
|
|
3054
|
-
The SDK exposes
|
|
3118
|
+
The SDK exposes three idempotency helpers — a generic function `idempotent()`, the step factory `idempotentStep()`, and its check-mode-aware sibling `checkStep()` — for the common case where a workflow step should:
|
|
3055
3119
|
|
|
3056
3120
|
1. **Check** whether the desired state is already in place.
|
|
3057
3121
|
2. **Apply** the change only when drift is detected.
|
|
3058
3122
|
3. **Surface** the resource (or its identifier) on both branches, so downstream steps don't need to know whether work happened or was skipped.
|
|
3059
3123
|
|
|
3060
|
-
|
|
3124
|
+
`idempotent()` and `idempotentStep()` wrap the same underlying runner and always apply on drift. Pick `idempotentStep()` when the operation is the whole job of a step; use `idempotent()` from anywhere — inside a multi-action step, a hook, or a bare async function. Pick `checkStep()` when the step should respect the run-level check mode — `kici run --check` previews the drift without applying it.
|
|
3061
3125
|
|
|
3062
3126
|
## `idempotent(options)`
|
|
3063
3127
|
|
|
@@ -3150,6 +3214,61 @@ export const setup = job('setup', {
|
|
|
3150
3214
|
});
|
|
3151
3215
|
```
|
|
3152
3216
|
|
|
3217
|
+
## `checkStep(name, options)`
|
|
3218
|
+
|
|
3219
|
+
The check-mode-aware sibling of `idempotentStep()`. It takes the **same option shape**, but behaves differently when a run is started in check mode (`kici run --check`):
|
|
3220
|
+
|
|
3221
|
+
| Factory | Behavior under `kici run --check` |
|
|
3222
|
+
| ---------------- | ----------------------------------------- |
|
|
3223
|
+
| `idempotentStep` | always applies on drift |
|
|
3224
|
+
| `checkStep` | reports drift, applies only in apply mode |
|
|
3225
|
+
|
|
3226
|
+
Use `checkStep()` for deploy-style steps where you want a dry-run preview of pending changes before committing them, and `idempotentStep()` for steps that must always converge (for example inside a hook). A `checkStep()` desugars to the first-class step check facet (`check` / `summarize` / `run(ctx, drift)` / `whenInSync`), so it participates in run-level check mode automatically: `kici run --check` reports the drift and skips `apply`, `kici run --check --fail-on-drift` exits non-zero when drift is detected, and apply mode applies the change.
|
|
3227
|
+
|
|
3228
|
+
### Parameters
|
|
3229
|
+
|
|
3230
|
+
| Name | Type | Required | Description |
|
|
3231
|
+
| ----------------- | ------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------- |
|
|
3232
|
+
| `name` | `string` | Yes | Step name. Appears in the run timeline and in log lines. |
|
|
3233
|
+
| `check` | `(ctx) => Promise<TDrift \| null>` | Yes | Read-only inspection. Return `null` when the system is already in the desired state. |
|
|
3234
|
+
| `apply` | `(ctx, drift: TDrift) => Promise<TApplied>` | Yes | Brings the system to the desired state. Runs only in apply mode (skipped under `kici run --check`). |
|
|
3235
|
+
| `summarize` | `(drift: TDrift) => string` | Yes | Human-readable summary of what `apply()` would do; shown in check-mode drift output. |
|
|
3236
|
+
| `whenInSync` | `(ctx) => Promise<TInSync>` | No | Runs when `check()` returned `null` (already in sync). |
|
|
3237
|
+
| `continueOnError` | `boolean` | No | When true, the job proceeds even if this step fails. |
|
|
3238
|
+
| `timeout` | `number` | No | Step-level timeout in milliseconds. |
|
|
3239
|
+
|
|
3240
|
+
The one signature difference from `idempotentStep`: `apply` and `whenInSync` receive `ctx` as their first argument, so the apply logic has access to `ctx.$`, `ctx.log`, and `ctx.secrets`.
|
|
3241
|
+
|
|
3242
|
+
### Result
|
|
3243
|
+
|
|
3244
|
+
`checkStep(...)` returns `Step<TApplied | TInSync>` — the output is whichever of `apply` / `whenInSync` ran.
|
|
3245
|
+
|
|
3246
|
+
### Example
|
|
3247
|
+
|
|
3248
|
+
```typescript
|
|
3249
|
+
import { checkStep, job } from '@kici-dev/sdk';
|
|
3250
|
+
|
|
3251
|
+
const ensureDnsRecord = checkStep('ensure-dns-record', {
|
|
3252
|
+
check: async (ctx) => {
|
|
3253
|
+
const existing = await ctx.$`dig +short api.example.com`;
|
|
3254
|
+
return existing.stdout.trim() ? null : { fqdn: 'api.example.com', target: '203.0.113.10' };
|
|
3255
|
+
},
|
|
3256
|
+
summarize: (drift) => `Create A record ${drift.fqdn} → ${drift.target}`,
|
|
3257
|
+
apply: async (ctx, drift) => {
|
|
3258
|
+
await ctx.$`dns-cli create ${drift.fqdn} ${drift.target}`;
|
|
3259
|
+
return { created: true };
|
|
3260
|
+
},
|
|
3261
|
+
whenInSync: async () => ({ created: false }),
|
|
3262
|
+
});
|
|
3263
|
+
|
|
3264
|
+
export const deploy = job('deploy', {
|
|
3265
|
+
runsOn: 'linux',
|
|
3266
|
+
steps: [ensureDnsRecord],
|
|
3267
|
+
});
|
|
3268
|
+
```
|
|
3269
|
+
|
|
3270
|
+
Run `kici run --check` against this workflow to see the drift summary without touching DNS; run it without `--check` to apply.
|
|
3271
|
+
|
|
3153
3272
|
## Worked example: create-if-missing returning a resource id
|
|
3154
3273
|
|
|
3155
3274
|
The typical use case is **resource provisioning that should be safe to re-run**. The helper guarantees the same downstream typed shape whether the resource already existed or was just created:
|
|
@@ -3192,8 +3311,8 @@ The caller never has to branch on outcome — `result.result` is always a `Bucke
|
|
|
3192
3311
|
|
|
3193
3312
|
## See also
|
|
3194
3313
|
|
|
3195
|
-
- [Core SDK reference](
|
|
3196
|
-
- [Runtime types](
|
|
3314
|
+
- [Core SDK reference](https://docs.kici.dev/user/sdk/core/) — the `step()`, `job()`, and `workflow()` factories that `idempotentStep()` builds on.
|
|
3315
|
+
- [Runtime types](https://docs.kici.dev/user/sdk/runtime/) — `StepContext`, `Logger`, and other surface used inside the helpers.
|
|
3197
3316
|
|
|
3198
3317
|
---
|
|
3199
3318
|
|
|
@@ -3320,7 +3439,7 @@ You can also narrow directly with `if (ctx.event.type === 'pull_request')` — T
|
|
|
3320
3439
|
|
|
3321
3440
|
`EventPayload` is a discriminated union over the `type` field. Each variant provides typed access to the normalized event fields and the raw webhook payload.
|
|
3322
3441
|
|
|
3323
|
-
Every variant carries the shared `EventBase` fields — `type`, `action`, `targetBranch`, `sourceBranch`, `provider`, `isForkPR`, `baseBranch`, `senderUsername`, `sourceRepo`, `changedFiles`, and the raw `payload` — plus a per-type `payload` shape for the typed variants. The complete field-by-field schema, including every typed `payload` shape and the shared GitHub object types, is in the [event payload reference](
|
|
3442
|
+
Every variant carries the shared `EventBase` fields — `type`, `action`, `targetBranch`, `sourceBranch`, `provider`, `isForkPR`, `baseBranch`, `senderUsername`, `sourceRepo`, `changedFiles`, and the raw `payload` — plus a per-type `payload` shape for the typed variants. The complete field-by-field schema, including every typed `payload` shape and the shared GitHub object types, is in the [event payload reference](https://docs.kici.dev/user/sdk/event-payloads/).
|
|
3324
3443
|
|
|
3325
3444
|
**Typed variants** (with GitHub-specific payload fields): `pull_request`, `push`, `tag`, `comment`, `review`, `review_comment`, `release`, `dispatch`, `create`, `delete`, `status`, `workflow_run`, `fork`, `star`, `watch`.
|
|
3326
3445
|
|
|
@@ -3430,7 +3549,7 @@ interface MatrixValues {
|
|
|
3430
3549
|
|
|
3431
3550
|
A matrix fan-out runs every combination at once by default. The fan-out-generic
|
|
3432
3551
|
`maxParallel` and `failFast` job options bound it the same way they bound a
|
|
3433
|
-
[`runsOnAll`](
|
|
3552
|
+
[`runsOnAll`](https://docs.kici.dev/user/sdk/runs-on-all/#rolling-rollout-maxparallel--failfast) host fan-out:
|
|
3434
3553
|
|
|
3435
3554
|
```typescript
|
|
3436
3555
|
const test = job('test', {
|
|
@@ -3583,7 +3702,7 @@ export default workflow('discovery-fan-out', { jobs: [discover, reports] });
|
|
|
3583
3702
|
| `'jobName'` / `{ name, when }` | `{ result, status }` — `result` is an `OutputProxy` (`ctx.needs.<job>.result.<step>.<field>`; single-step `run` jobs flatten to `ctx.needs.<job>.result.<field>`); `status` is the upstream's terminal status |
|
|
3584
3703
|
| `dynamicGroup('g')` / `dynamicGroup('g', { when })` | ordered array of `{ name, result, status }`, one per group member |
|
|
3585
3704
|
|
|
3586
|
-
`ctx.needs` is deterministic — a snapshot of upstream outputs frozen at first eval and replayed unchanged on re-eval, like `ctx.event`. Use result-aware generation for same-run fan-out from a prior job's result; use [`jobComplete()`](
|
|
3705
|
+
`ctx.needs` is deterministic — a snapshot of upstream outputs frozen at first eval and replayed unchanged on re-eval, like `ctx.event`. Use result-aware generation for same-run fan-out from a prior job's result; use [`jobComplete()`](https://docs.kici.dev/user/sdk/triggers/) for cross-workflow reactions to a job finishing. See the architecture deep-dive in [dynamic jobs](https://docs.kici.dev/architecture/execution/dynamic-jobs/#result-aware-generation).
|
|
3587
3706
|
|
|
3588
3707
|
### JobOrFactory
|
|
3589
3708
|
|
|
@@ -3624,7 +3743,7 @@ roster that matches a label predicate — one pinned execution per host. Use it
|
|
|
3624
3743
|
fleet-wide operations: patch every web tier, smoke-test every node, collect uptime
|
|
3625
3744
|
from the fleet.
|
|
3626
3745
|
|
|
3627
|
-
`runsOnAll` is mutually exclusive with [`runsOn`](/user/sdk/core/): a job declares one
|
|
3746
|
+
`runsOnAll` is mutually exclusive with [`runsOn`](https://docs.kici.dev/user/sdk/core/): a job declares one
|
|
3628
3747
|
or the other. Where `runsOn` picks a **single** agent that satisfies the labels,
|
|
3629
3748
|
`runsOnAll` targets **all** matching hosts and runs the job once on each, pinned to
|
|
3630
3749
|
that specific host.
|
|
@@ -3673,7 +3792,7 @@ const patch = job('patch', {
|
|
|
3673
3792
|
#### Targeting by pattern
|
|
3674
3793
|
|
|
3675
3794
|
Every entry in any of these forms — include or exclude — can be an exact string, a
|
|
3676
|
-
glob, or a regular expression, exactly like [`runsOn`](
|
|
3795
|
+
glob, or a regular expression, exactly like [`runsOn`](https://docs.kici.dev/user/sdk/core/#targeting-by-pattern):
|
|
3677
3796
|
|
|
3678
3797
|
- **Plain string → exact match** (`'role:web'`).
|
|
3679
3798
|
- **String with glob metacharacters (`*`, `?`, `[]`, `{}`) → glob** (`'kici:host:web-*'`).
|
|
@@ -3725,6 +3844,66 @@ run: async (ctx) => {
|
|
|
3725
3844
|
|
|
3726
3845
|
Both are `undefined` for jobs that do not use `runsOnAll`.
|
|
3727
3846
|
|
|
3847
|
+
### ctx.fanout — fan-out position
|
|
3848
|
+
|
|
3849
|
+
Every fan-out child — a `runsOnAll` host **or** a matrix combination — also
|
|
3850
|
+
carries its **position** within the fan-out:
|
|
3851
|
+
|
|
3852
|
+
```typescript
|
|
3853
|
+
ctx.fanout?: {
|
|
3854
|
+
index: number; // 0-based position in the deterministically-ordered fan-out
|
|
3855
|
+
total: number; // number of children in this fan-out
|
|
3856
|
+
first: boolean; // index === 0
|
|
3857
|
+
last: boolean; // index === total - 1
|
|
3858
|
+
};
|
|
3859
|
+
```
|
|
3860
|
+
|
|
3861
|
+
The order is a **documented guarantee**: host fan-out is ordered by agent id,
|
|
3862
|
+
matrix fan-out by its combination label. So `ctx.fanout.first` is always the
|
|
3863
|
+
same (lowest-agent-id) host across re-runs, and `ctx.fanout.last` the same final
|
|
3864
|
+
one. `ctx.fanout` is `undefined` on a job that is not fanned out.
|
|
3865
|
+
|
|
3866
|
+
### Run-once steps: onlyOnFirstHost / onlyOnLastHost / onlyOnFanoutIndex
|
|
3867
|
+
|
|
3868
|
+
For ordered, stateful rollouts you often need a step that runs on exactly **one**
|
|
3869
|
+
host — enable a leader before the rest join, run a one-time migration, take a
|
|
3870
|
+
single dump. Three rule helpers express this by reading `ctx.fanout`:
|
|
3871
|
+
|
|
3872
|
+
```typescript
|
|
3873
|
+
import { job, step, onlyOnFirstHost, onlyOnLastHost, onlyOnFanoutIndex } from '@kici-dev/sdk';
|
|
3874
|
+
|
|
3875
|
+
const rollout = job('rollout', {
|
|
3876
|
+
runsOnAll: 'role:db',
|
|
3877
|
+
maxParallel: 1, // serial, so "first" runs before the rest
|
|
3878
|
+
steps: [
|
|
3879
|
+
// Runs only on the first (lowest-agent-id) host — KiCI's run-once primitive.
|
|
3880
|
+
step('enable-sync-mode', { rules: [onlyOnFirstHost()] }, async (ctx) => {
|
|
3881
|
+
/* configure the leader before standbys join */
|
|
3882
|
+
}),
|
|
3883
|
+
// Runs on every host.
|
|
3884
|
+
step('apply', async (ctx) => {
|
|
3885
|
+
/* ... */
|
|
3886
|
+
}),
|
|
3887
|
+
// Runs only on the last host.
|
|
3888
|
+
step('finalize', { rules: [onlyOnLastHost()] }, async (ctx) => {
|
|
3889
|
+
/* ... */
|
|
3890
|
+
}),
|
|
3891
|
+
],
|
|
3892
|
+
});
|
|
3893
|
+
```
|
|
3894
|
+
|
|
3895
|
+
- A step gated this way is **skipped** (not failed) on non-matching hosts — its
|
|
3896
|
+
outputs exist only on the host where it ran.
|
|
3897
|
+
- `onlyOnFanoutIndex(n)` targets the host at a specific position.
|
|
3898
|
+
- **Non-fan-out safety:** on a job that is not fanned out, `ctx.fanout` is
|
|
3899
|
+
`undefined` and these helpers treat the job as a single implicit child at
|
|
3900
|
+
index 0 — so `onlyOnFirstHost()` runs normally there (there is one host, which
|
|
3901
|
+
is the first). This means you can author a step with `onlyOnFirstHost()` and it
|
|
3902
|
+
behaves correctly whether or not the job ends up fanning out.
|
|
3903
|
+
- The helpers are host-flavored by name (the dominant use case) but read
|
|
3904
|
+
`ctx.fanout`, so they work for matrix fan-out too — `onlyOnFirstHost()` runs on
|
|
3905
|
+
the first combination.
|
|
3906
|
+
|
|
3728
3907
|
### byHost outputs
|
|
3729
3908
|
|
|
3730
3909
|
A downstream that `needs:` a `runsOnAll` job receives a **byHost** envelope instead
|
|
@@ -3757,7 +3936,7 @@ to a single scalar: `summary.outputs[key]` is an array of every host's value, an
|
|
|
3757
3936
|
### onUnreachable: skip | fail | hold
|
|
3758
3937
|
|
|
3759
3938
|
Resolution is backed by the **declared host roster** (see the operator
|
|
3760
|
-
[host roster](/operator/orchestrator/host-roster/) doc), not just the live registry.
|
|
3939
|
+
[host roster](https://docs.kici.dev/operator/orchestrator/host-roster/) doc), not just the live registry.
|
|
3761
3940
|
This lets KiCI surface an expected-but-absent host instead of silently fanning out to a
|
|
3762
3941
|
partial fleet. The `onUnreachable` policy controls what happens when a **durable**
|
|
3763
3942
|
(static) host in the roster is matched but not currently connected:
|
|
@@ -3783,6 +3962,37 @@ independent of `onUnreachable` — a scaled-down node may never return. A `runsO
|
|
|
3783
3962
|
that matches zero usable hosts fails the run rather than reporting a silent zero-child
|
|
3784
3963
|
success.
|
|
3785
3964
|
|
|
3965
|
+
### includeUninitialized: converge a fresh fleet
|
|
3966
|
+
|
|
3967
|
+
`onUnreachable` governs declared hosts that _had_ an agent and are momentarily absent.
|
|
3968
|
+
A **never-initialized** host — a freshly-provisioned box reachable over SSH but with no
|
|
3969
|
+
agent yet — is a different case: there is nothing to run on. Set
|
|
3970
|
+
`includeUninitialized: true` to widen the fan-out to those hosts and bring them up:
|
|
3971
|
+
|
|
3972
|
+
```typescript
|
|
3973
|
+
const converge = job('converge', {
|
|
3974
|
+
runsOnAll: 'kici:group:prod',
|
|
3975
|
+
includeUninitialized: true,
|
|
3976
|
+
steps: [partitionDisk, formatLuks, debootstrap, installAgent],
|
|
3977
|
+
});
|
|
3978
|
+
```
|
|
3979
|
+
|
|
3980
|
+
For each un-agented declared host (one carrying SSH reach metadata), KiCI brings up a
|
|
3981
|
+
temporary init-runner over SSH and runs the **same steps** on it; hosts that already
|
|
3982
|
+
have a live agent run the steps on their own agent. One workflow converges the whole
|
|
3983
|
+
fleet — fresh boxes get built, live boxes run the same phases.
|
|
3984
|
+
|
|
3985
|
+
Because the steps run on already-initialized hosts too, the bootstrap phases **must be
|
|
3986
|
+
idempotent [check-steps](https://docs.kici.dev/user/sdk/core/)**: each step's `check()` reports in-sync on a
|
|
3987
|
+
live box so the partition / format / install steps **skip** there and run only on fresh
|
|
3988
|
+
boxes. This is the safety guard — an OS or disk-format step must never re-run on a host
|
|
3989
|
+
that is already built. Re-running the workflow is a no-op everywhere. See the operator
|
|
3990
|
+
[fresh-box bootstrap](https://docs.kici.dev/operator/orchestrator/host-roster/) doc for the bring-up,
|
|
3991
|
+
capability gating, and lifecycle details.
|
|
3992
|
+
|
|
3993
|
+
`includeUninitialized` is only meaningful alongside `runsOnAll`; it is ignored on a
|
|
3994
|
+
single-agent `runsOn` job.
|
|
3995
|
+
|
|
3786
3996
|
### Rolling rollout: maxParallel + failFast
|
|
3787
3997
|
|
|
3788
3998
|
By default a `runsOnAll` fan-out dispatches to every matched host at once — fine for
|
|
@@ -3840,8 +4050,8 @@ When `--target` narrows a `runsOnAll` job to zero hosts, the run **fails** by de
|
|
|
3840
4050
|
(a mistyped selector should be loud, not silently no-op). Pass `--target-allow-empty`
|
|
3841
4051
|
to **skip** the zeroed job instead — it records a `skipped` status, and downstream jobs
|
|
3842
4052
|
gated with `when: 'on-skip'` (or `when: 'always'`) still run, exactly as for an
|
|
3843
|
-
`onUnreachable: 'skip'` zero-host fan-out. See the [CLI reference](/user/cli-reference/#host-narrowing-with---target)
|
|
3844
|
-
for the full flag behavior and the [`needs` gating model](
|
|
4053
|
+
`onUnreachable: 'skip'` zero-host fan-out. See the [CLI reference](https://docs.kici.dev/user/cli-reference/#host-narrowing-with---target)
|
|
4054
|
+
for the full flag behavior and the [`needs` gating model](https://docs.kici.dev/user/sdk/core/#job-dependencies-needs)
|
|
3845
4055
|
for how a skipped upstream propagates.
|
|
3846
4056
|
|
|
3847
4057
|
### Limits (v0)
|
|
@@ -3875,9 +4085,9 @@ All types are exported from `@kici-dev/sdk` as type-only imports.
|
|
|
3875
4085
|
| `OutputSchema` | Record of Zod types for step outputs |
|
|
3876
4086
|
| `InferOutputs<T>` | Infer output type from output schema |
|
|
3877
4087
|
| `ContainerConfig` | Container config for job execution (`image`, `env?`) |
|
|
3878
|
-
| `RunsOn` | Union of `runsOn` forms: `string \| RegExp \| (string \| RegExp)[] \| RunsOnSelector`. A plain string matches exactly, a string with glob metacharacters (`*?[]{}`) is a glob, and a `RegExp` is a regular expression. See [Targeting by pattern](
|
|
4088
|
+
| `RunsOn` | Union of `runsOn` forms: `string \| RegExp \| (string \| RegExp)[] \| RunsOnSelector`. A plain string matches exactly, a string with glob metacharacters (`*?[]{}`) is a glob, and a `RegExp` is a regular expression. See [Targeting by pattern](https://docs.kici.dev/user/sdk/core/#targeting-by-pattern). |
|
|
3879
4089
|
| `RunsOnSelector` | Object form for `runsOn` with `labels` (required) and `exclude` (optional) properties. Each element accepts the exact / glob / regex forms on both sides. |
|
|
3880
|
-
| `RunsOnAllInput` | Union of `runsOnAll` host fan-out forms: `string \| RegExp \| (string \| RegExp)[] \| { include: { all: (string \| RegExp)[] }[]; exclude?: (string \| RegExp)[] }`. Same exact / glob / regex semantics per element. See [runsOnAll](
|
|
4090
|
+
| `RunsOnAllInput` | Union of `runsOnAll` host fan-out forms: `string \| RegExp \| (string \| RegExp)[] \| { include: { all: (string \| RegExp)[] }[]; exclude?: (string \| RegExp)[] }`. Same exact / glob / regex semantics per element. See [runsOnAll](https://docs.kici.dev/user/sdk/runs-on-all/#targeting-by-pattern). |
|
|
3881
4091
|
| `Fixture` | Test fixture definition returned by `fixture()` |
|
|
3882
4092
|
| `FixtureOptions` | Options for `fixture()` factory |
|
|
3883
4093
|
| `Registry` | Private npm registry declaration used in `WorkflowOptions.registries` |
|
|
@@ -4064,7 +4274,7 @@ step('example', async ({ $, log, env, matrix, workflow, job }) => {
|
|
|
4064
4274
|
- **Inside a dynamic `environment` / `env` / `concurrencyGroup` function** on a static job — captured to the `__init__` job's synthetic step-0 log, which appears in the timeline as "Init: _jobname_".
|
|
4065
4275
|
- **Inside a `DynamicJobFn` body and the per-generated-job `environment` / `env` / `concurrencyGroup` / `matrix` functions** — captured to the `__dynamic__` job's synthetic step-0 log ("Evaluate: _jobname_" in the timeline). The `$` parameter in that context is a scoped zx shell, so `await $\`...\`` subprocess output is captured too.
|
|
4066
4276
|
|
|
4067
|
-
Use whichever style is convenient — you don't have to wrap `console.log` in the provided `log` parameter to make it visible. One limitation applies to in-process contexts only (init, build, dynamic-eval): direct `process.stdout.write` / `printf` is not captured there, because the agent's own logger uses that path and we don't want agent-internal output leaking into your step logs. Use `console.*` or the `log` parameter instead. See [Log streaming](
|
|
4277
|
+
Use whichever style is convenient — you don't have to wrap `console.log` in the provided `log` parameter to make it visible. One limitation applies to in-process contexts only (init, build, dynamic-eval): direct `process.stdout.write` / `printf` is not captured there, because the agent's own logger uses that path and we don't want agent-internal output leaking into your step logs. Use `console.*` or the `log` parameter instead. See [Log streaming](https://docs.kici.dev/architecture/execution/job-execution/#log-streaming) for the full capture surface and limits (default 10 MB per step, backpressure behavior).
|
|
4068
4278
|
|
|
4069
4279
|
### setEnv(key, value)
|
|
4070
4280
|
|
|
@@ -4089,7 +4299,7 @@ step('use', async (ctx) => {
|
|
|
4089
4299
|
- Last-write-wins -- if multiple steps set the same key, the last value is used
|
|
4090
4300
|
- Cannot override operator-injected secrets (the operator value takes precedence)
|
|
4091
4301
|
- Changes take effect immediately in the current step and persist for all subsequent steps
|
|
4092
|
-
- Shell commands export the same way by appending to `$KICI_ENV` (see [Exporting env from shell commands](
|
|
4302
|
+
- Shell commands export the same way by appending to `$KICI_ENV` (see [Exporting env from shell commands](https://docs.kici.dev/user/sdk/runtime/#exporting-env-from-shell-commands-kici_env--kici_path) below)
|
|
4093
4303
|
|
|
4094
4304
|
### addPath(dir)
|
|
4095
4305
|
|
|
@@ -4302,7 +4512,7 @@ const publish = job('publish', {
|
|
|
4302
4512
|
- The returned `{ storageKey, subjectDigest, bundleMediaType }` identifies the stored bundle.
|
|
4303
4513
|
- Only available inside a running job step; calling it outside one (for example, during local execution) rejects with a clear error.
|
|
4304
4514
|
|
|
4305
|
-
See the [build provenance guide](
|
|
4515
|
+
See the [build provenance guide](https://docs.kici.dev/user/provenance/) for the end-to-end attest →
|
|
4306
4516
|
verify → view journey, including how to verify a bundle with `kici verify-attestation`.
|
|
4307
4517
|
|
|
4308
4518
|
## Secrets
|
|
@@ -4397,7 +4607,7 @@ step('discover', async (ctx) => {
|
|
|
4397
4607
|
|
|
4398
4608
|
### File-mounted secrets (ctx.secrets.mountFile / exposeFile)
|
|
4399
4609
|
|
|
4400
|
-
Tools that require a file path on disk (sops `SOPS_AGE_KEY_FILE`, kubectl `KUBECONFIG`, gcloud `GOOGLE_APPLICATION_CREDENTIALS`) get a typed step-side API: `ctx.secrets.mountFile(opts)` writes the concatenation of one or more existing secrets to a per-step tmpfile and returns the path; `ctx.secrets.exposeFile(envVar, opts)` additionally sets `process.env[envVar] = path`. Files are removed and env vars are unset automatically when the step completes (success, failure, or timeout) — no manual cleanup. See [Mounting secrets as files](
|
|
4610
|
+
Tools that require a file path on disk (sops `SOPS_AGE_KEY_FILE`, kubectl `KUBECONFIG`, gcloud `GOOGLE_APPLICATION_CREDENTIALS`) get a typed step-side API: `ctx.secrets.mountFile(opts)` writes the concatenation of one or more existing secrets to a per-step tmpfile and returns the path; `ctx.secrets.exposeFile(envVar, opts)` additionally sets `process.env[envVar] = path`. Files are removed and env vars are unset automatically when the step completes (success, failure, or timeout) — no manual cleanup. See [Mounting secrets as files](https://docs.kici.dev/user/secrets/#mounting-secrets-as-files) for the full options table, lifecycle details, and the canonical sops example.
|
|
4401
4611
|
|
|
4402
4612
|
### Local test mode secrets
|
|
4403
4613
|
|
|
@@ -4650,13 +4860,73 @@ Create a repository_dispatch trigger. Returns a frozen `DispatchTriggerConfig`.
|
|
|
4650
4860
|
function dispatch(config?: DispatchConfigInput): DispatchTriggerConfig;
|
|
4651
4861
|
```
|
|
4652
4862
|
|
|
4653
|
-
**Config options:** `types` (string[]), `description`
|
|
4863
|
+
**Config options:** `types` (string[]), `description`, `inputs` (typed dispatch inputs map)
|
|
4654
4864
|
|
|
4655
4865
|
```typescript
|
|
4656
4866
|
dispatch(); // Any dispatch
|
|
4657
4867
|
dispatch({ types: ['deploy', 'rollback'] }); // Specific event types
|
|
4658
4868
|
```
|
|
4659
4869
|
|
|
4870
|
+
#### Typed dispatch inputs
|
|
4871
|
+
|
|
4872
|
+
A `dispatch()` trigger can declare a typed `inputs` schema. Operators supply
|
|
4873
|
+
values with `kici run --input key=value`; KiCI validates, coerces, defaults, and
|
|
4874
|
+
exposes them to steps and rules as `ctx.dispatchInputs`. The values are validated
|
|
4875
|
+
on the orchestrator from the compiled lock file — a missing required input or a
|
|
4876
|
+
bad value is rejected before any agent runs, without cloning the repository.
|
|
4877
|
+
|
|
4878
|
+
```typescript
|
|
4879
|
+
import { workflow, job, step, dispatch, defineDispatchInputs, z } from '@kici-dev/sdk';
|
|
4880
|
+
|
|
4881
|
+
const inputs = defineDispatchInputs({
|
|
4882
|
+
target: z.string().optional(),
|
|
4883
|
+
skipCveScan: z.boolean().default(false),
|
|
4884
|
+
skipCveScanReason: z.string().min(1).optional(),
|
|
4885
|
+
mode: z.enum(['full', 'edge-only']).default('full'),
|
|
4886
|
+
retries: z.number().int().min(0).max(10).default(3),
|
|
4887
|
+
});
|
|
4888
|
+
|
|
4889
|
+
export default workflow('deploy-prod', {
|
|
4890
|
+
on: dispatch({ types: ['deploy-prod'], inputs }),
|
|
4891
|
+
jobs: [
|
|
4892
|
+
job('gates', {
|
|
4893
|
+
runsOn: 'kici:group:ops',
|
|
4894
|
+
steps: [
|
|
4895
|
+
step('cve-gate', async (ctx) => {
|
|
4896
|
+
const i = inputs.from(ctx); // fully typed per declared key
|
|
4897
|
+
if (i.skipCveScan) {
|
|
4898
|
+
ctx.log.warn(`CVE gate skipped: ${i.skipCveScanReason ?? '(no reason)'}`);
|
|
4899
|
+
return;
|
|
4900
|
+
}
|
|
4901
|
+
await ctx.$`pnpm scan:cve:gate`;
|
|
4902
|
+
}),
|
|
4903
|
+
],
|
|
4904
|
+
}),
|
|
4905
|
+
],
|
|
4906
|
+
});
|
|
4907
|
+
```
|
|
4908
|
+
|
|
4909
|
+
- **`defineDispatchInputs(map)`** is the single declaration site. It returns a
|
|
4910
|
+
handle that `dispatch({ inputs })` accepts and exposes `inputs.from(ctx)` — a
|
|
4911
|
+
typed reader over `ctx.dispatchInputs`, typed per declared key. `dispatch({ inputs })`
|
|
4912
|
+
also accepts a bare `{ name: schema }` map directly when you don't need the reader.
|
|
4913
|
+
- **`ctx.dispatchInputs`** is always present (a validated map of
|
|
4914
|
+
`string | number | boolean | null`), distinct from `ctx.inputs` (typed outputs
|
|
4915
|
+
from `needs` dependencies). Rules see the same values via `ctx.dispatchInputs`,
|
|
4916
|
+
so `skipUnless(ctx => !ctx.dispatchInputs.skipCveScan)` works.
|
|
4917
|
+
- **Defaults are applied once**, on the orchestrator (the authoritative side); the
|
|
4918
|
+
CLI pre-validates `--input` for fast feedback and forwards the raw operator pairs.
|
|
4919
|
+
|
|
4920
|
+
**Allowed input types (closed subset):** `z.string()`, `z.number()`,
|
|
4921
|
+
`z.boolean()`, `z.enum([...])`, `z.literal(v)`, with the modifiers `.optional()`,
|
|
4922
|
+
`.nullable()`, `.default(v)`, `.min(n)`, `.max(n)`, `.regex(re)`, `.int()`.
|
|
4923
|
+
Anything outside this set (`.refine()`, `.transform()`, `.pipe()`, `z.object()`,
|
|
4924
|
+
`z.array()`, `z.union()`, `z.record()`, `z.coerce.*`) is a **compile error** —
|
|
4925
|
+
the closed set is what guarantees the schema survives the trip to the
|
|
4926
|
+
orchestrator's lock file without silently dropping any validation. CLI strings
|
|
4927
|
+
are coerced for you (`--input retries=3` becomes the number `3`; booleans accept
|
|
4928
|
+
`true`/`false`/`1`/`0`/`yes`/`no`), so author your schema with clean types.
|
|
4929
|
+
|
|
4660
4930
|
### create()
|
|
4661
4931
|
|
|
4662
4932
|
Create a ref creation trigger (branches/tags). Returns a frozen `CreateTriggerConfig`.
|
|
@@ -4770,7 +5040,7 @@ Create a catch-all webhook trigger for any GitHub event. Returns a frozen `Webho
|
|
|
4770
5040
|
function webhook(config: WebhookConfigInput): WebhookTriggerConfig;
|
|
4771
5041
|
```
|
|
4772
5042
|
|
|
4773
|
-
**Config options:** `events` (required string[]), `actions` (optional string[]), `repos` (optional cross-repo source patterns -- see [global workflows](
|
|
5043
|
+
**Config options:** `events` (required string[]), `actions` (optional string[]), `repos` (optional cross-repo source patterns -- see [global workflows](https://docs.kici.dev/user/global-workflows/)), `description`
|
|
4774
5044
|
|
|
4775
5045
|
```typescript
|
|
4776
5046
|
webhook({ events: ['deployment'] }); // Deployment events
|
|
@@ -4875,7 +5145,7 @@ jobComplete({ workflow: 'CI', job: 'build', status: ['success'] }); // Success o
|
|
|
4875
5145
|
jobComplete({ workflow: 'CI', job: 'build', source: 'org/repo' }); // Cross-repo
|
|
4876
5146
|
```
|
|
4877
5147
|
|
|
4878
|
-
`jobComplete()` starts a **new** workflow run that reacts to another job finishing (gated on the prior job's status). For same-run fan-out — generating follow-up jobs from a prior job's _outputs_ within the same run — use a result-aware [`dynamicJob(group, { needs, generate })`](
|
|
5148
|
+
`jobComplete()` starts a **new** workflow run that reacts to another job finishing (gated on the prior job's status). For same-run fan-out — generating follow-up jobs from a prior job's _outputs_ within the same run — use a result-aware [`dynamicJob(group, { needs, generate })`](https://docs.kici.dev/user/sdk/rules-matrix-dynamic/#dynamicjob--result-aware-generation) instead.
|
|
4879
5149
|
|
|
4880
5150
|
### genericWebhook()
|
|
4881
5151
|
|
|
@@ -5255,9 +5525,9 @@ If `check()` throws while polling, the error is logged and polling continues —
|
|
|
5255
5525
|
|
|
5256
5526
|
## See also
|
|
5257
5527
|
|
|
5258
|
-
- [Core SDK reference](
|
|
5259
|
-
- [Idempotent helpers](
|
|
5260
|
-
- [Runtime types](
|
|
5528
|
+
- [Core SDK reference](https://docs.kici.dev/user/sdk/core/) — the `step()`, `job()`, and `workflow()` factories that `waitForStep()` builds on.
|
|
5529
|
+
- [Idempotent helpers](https://docs.kici.dev/user/sdk/idempotent/) — `idempotent()` and `idempotentStep()` for check / apply patterns.
|
|
5530
|
+
- [Runtime types](https://docs.kici.dev/user/sdk/runtime/) — `StepContext`, `Logger`, and other surface used inside the helpers.
|
|
5261
5531
|
|
|
5262
5532
|
---
|
|
5263
5533
|
|
|
@@ -5269,14 +5539,14 @@ Reference documentation for `@kici-dev/sdk`. The reference is split across five
|
|
|
5269
5539
|
|
|
5270
5540
|
| Page | Covers |
|
|
5271
5541
|
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
5272
|
-
| [Core](
|
|
5273
|
-
| [Triggers](
|
|
5274
|
-
| [Rules, matrix, dynamic jobs](
|
|
5275
|
-
| [Caching](
|
|
5276
|
-
| [Validation & events](
|
|
5277
|
-
| [Runtime](
|
|
5278
|
-
| [Idempotent helpers](
|
|
5279
|
-
| [Wait-for helpers](
|
|
5542
|
+
| [Core](https://docs.kici.dev/user/sdk/core/) | `workflow()`, `job()`, `step()` factory functions and step / job authoring patterns (bare functions, output chaining, `needs`, dynamic groups). |
|
|
5543
|
+
| [Triggers](https://docs.kici.dev/user/sdk/triggers/) | All 22 trigger factories -- GitHub events (`pr`, `push`, `tag`, `comment`, ...), event triggers (`kiciEvent`, `workflowComplete`, `jobComplete`), `genericWebhook`, `schedule`, `lifecycle`, plus branch-pattern semantics. |
|
|
5544
|
+
| [Rules, matrix, dynamic jobs](https://docs.kici.dev/user/sdk/rules-matrix-dynamic/) | `rule()`, `skip()`, matrix builds (static + dynamic), and `dynamicJob()` / `dynamicGroup()`. |
|
|
5545
|
+
| [Caching](https://docs.kici.dev/user/sdk/caching/) | `CacheSpec`, declarative `cache` on jobs/steps, imperative `ctx.cache.restore()` / `ctx.cache.save()`, immutable keys, `restoreKeys` prefix fallback, per-org + per-ref isolation. |
|
|
5546
|
+
| [Validation & events](https://docs.kici.dev/user/sdk/validation-events/) | `validateDag()`, `defineEvent()`, event emission patterns. |
|
|
5547
|
+
| [Runtime](https://docs.kici.dev/user/sdk/runtime/) | Types index, `StepContext`, secrets, and fixtures. |
|
|
5548
|
+
| [Idempotent helpers](https://docs.kici.dev/user/sdk/idempotent/) | `idempotent()`, `idempotentStep()`, and the check-mode-aware `checkStep()` — check / apply pattern with typed results on both the skipped and applied branches. |
|
|
5549
|
+
| [Wait-for helpers](https://docs.kici.dev/user/sdk/wait-for/) | `waitFor()` and `waitForStep()` — poll a condition on an interval, run an optional success action, recover gracefully on timeout. |
|
|
5280
5550
|
|
|
5281
5551
|
The `@kici-dev/sdk` package re-exports the entire surface from a single entry point. Pick what you need:
|
|
5282
5552
|
|
|
@@ -5288,12 +5558,12 @@ For the complete list of every named export (factory functions, triggers, rules,
|
|
|
5288
5558
|
|
|
5289
5559
|
## See also
|
|
5290
5560
|
|
|
5291
|
-
- [Getting started](getting-started
|
|
5292
|
-
- [CLI reference](cli-reference
|
|
5293
|
-
- [Workflow patterns](workflow-patterns
|
|
5294
|
-
- [Secrets management (operator)](
|
|
5295
|
-
- [Secrets architecture](
|
|
5296
|
-
- [State machine](
|
|
5561
|
+
- [Getting started](https://docs.kici.dev/user/getting-started/) -- install the SDK, write your first workflow, test locally
|
|
5562
|
+
- [CLI reference](https://docs.kici.dev/user/cli-reference/) -- compile, test, and manage workflows from the command line
|
|
5563
|
+
- [Workflow patterns](https://docs.kici.dev/user/workflow-patterns/) -- common patterns using the SDK features documented above
|
|
5564
|
+
- [Secrets management (operator)](https://docs.kici.dev/operator/security/secrets/) -- configure encrypted secret storage and admin API
|
|
5565
|
+
- [Secrets architecture](https://docs.kici.dev/architecture/security/secrets/) -- encryption model, multi-backend, and data flow
|
|
5566
|
+
- [State machine](https://docs.kici.dev/architecture/execution/state-machine/) -- how execution states map to the lifecycle of jobs and steps
|
|
5297
5567
|
|
|
5298
5568
|
---
|
|
5299
5569
|
|
|
@@ -5458,13 +5728,13 @@ Every request to `/api/v1/*` carries an `Authorization: Bearer <token>` header.
|
|
|
5458
5728
|
| `kici_sa_` | Service account key | Dashboard → Settings → Service accounts | Org |
|
|
5459
5729
|
| (other) | OIDC JWT or opaque OIDC token | OIDC login (browser SPA) | User (cross-org) |
|
|
5460
5730
|
|
|
5461
|
-
JWT and opaque OIDC tokens are validated against the configured OIDC issuer (JWKS for JWTs, the issuer's UserInfo endpoint for opaque ones). All `kici_*` tokens are validated by SHA-256 hash lookup against the Platform DB. See [RBAC: authentication methods](
|
|
5731
|
+
JWT and opaque OIDC tokens are validated against the configured OIDC issuer (JWKS for JWTs, the issuer's UserInfo endpoint for opaque ones). All `kici_*` tokens are validated by SHA-256 hash lookup against the Platform DB. See [RBAC: authentication methods](https://docs.kici.dev/architecture/security/rbac/#authentication-methods) for the full model.
|
|
5462
5732
|
|
|
5463
5733
|
> **Note:** `kici_ok_` keys are **not** for the HTTP API — they authenticate orchestrator-to-Platform WebSocket connections only. Use `kici_sk_` (or `kici_pat_`) for HTTP calls.
|
|
5464
5734
|
|
|
5465
5735
|
### Permissions
|
|
5466
5736
|
|
|
5467
|
-
Tokens authenticate; RBAC authorizes. Every org-scoped route runs `orgContextMiddleware` (verifies you are a member of the target org) followed by `requirePermission(resource, level)`. The
|
|
5737
|
+
Tokens authenticate; RBAC authorizes. Every org-scoped route runs `orgContextMiddleware` (verifies you are a member of the target org) followed by `requirePermission(resource, level)`. The 17 resources and 5 levels are documented in [RBAC](https://docs.kici.dev/architecture/security/rbac/#permission-model). User API keys carry their own permission matrix bounded above by the creator's effective permissions; PATs inherit the user's role permissions (or are capped further by their `scopes` field).
|
|
5468
5738
|
|
|
5469
5739
|
### Configurable surfaces
|
|
5470
5740
|
|
|
@@ -5509,7 +5779,7 @@ There is currently no per-token rate limit on `/api/v1/*`. A single global body-
|
|
|
5509
5779
|
|
|
5510
5780
|
### Audit trail
|
|
5511
5781
|
|
|
5512
|
-
Every `/api/v1/*` mutation that touches tenant-plane data is recorded in the upstream tenant-plane audit log, stamped with the actor (user, API key, service account, or upstream operator on a break-glass support read). Reads on customer data go through the orchestrator over the WebSocket proxy and land in the orchestrator's `access_log` table. See [Audit log](
|
|
5782
|
+
Every `/api/v1/*` mutation that touches tenant-plane data is recorded in the upstream tenant-plane audit log, stamped with the actor (user, API key, service account, or upstream operator on a break-glass support read). Reads on customer data go through the orchestrator over the WebSocket proxy and land in the orchestrator's `access_log` table. See [Audit log](https://docs.kici.dev/operator/security/audit-log/) for the orchestrator schema and the dashboard's "Activity" page for the federated view.
|
|
5513
5783
|
|
|
5514
5784
|
## Token storage
|
|
5515
5785
|
|
|
@@ -5655,26 +5925,27 @@ kici run local [event] [options]
|
|
|
5655
5925
|
|
|
5656
5926
|
**Options:**
|
|
5657
5927
|
|
|
5658
|
-
| Option
|
|
5659
|
-
|
|
|
5660
|
-
| `-p, --pick`
|
|
5661
|
-
| `--workflow <name>`
|
|
5662
|
-
| `--job <name>`
|
|
5663
|
-
| `--branch <name>`
|
|
5664
|
-
| `--sha <hash>`
|
|
5665
|
-
| `--payload <path>`
|
|
5666
|
-
| `--concurrency <n>`
|
|
5667
|
-
| `--keep-going`
|
|
5668
|
-
| `--container`
|
|
5669
|
-
| `--env <KEY=VALUE>`
|
|
5670
|
-
| `--
|
|
5671
|
-
| `--
|
|
5672
|
-
| `--
|
|
5673
|
-
| `--
|
|
5674
|
-
| `--
|
|
5675
|
-
| `--
|
|
5676
|
-
| `--
|
|
5677
|
-
| `--
|
|
5928
|
+
| Option | Default | Description |
|
|
5929
|
+
| --------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
5930
|
+
| `-p, --pick` | `false` | Interactively pick a workflow + trigger (see below) |
|
|
5931
|
+
| `--workflow <name>` | none | Run only the specified workflow (mutex with `--pick`) |
|
|
5932
|
+
| `--job <name>` | none | Run only the specified job (and its dependencies) |
|
|
5933
|
+
| `--branch <name>` | detected | Override detected git branch |
|
|
5934
|
+
| `--sha <hash>` | detected | Override detected git SHA |
|
|
5935
|
+
| `--payload <path>` | none | Path to explicit event payload JSON file |
|
|
5936
|
+
| `--concurrency <n>` | CPU cores | Max parallel jobs **within one run** (job-level only). Cross-run [concurrency groups](https://docs.kici.dev/user/concurrency/) declared in `workflow({ concurrency: ... })` are enforced separately — see "Concurrency enforcement" below. |
|
|
5937
|
+
| `--keep-going` | `false` | Continue after job failure |
|
|
5938
|
+
| `--container` | `false` | Use Podman container isolation |
|
|
5939
|
+
| `--env <KEY=VALUE>` | none | Environment variable override (repeatable) |
|
|
5940
|
+
| `--input <KEY=VALUE>` | none | Typed workflow-dispatch input (repeatable) — coerced + validated against the workflow's `dispatch({ inputs })` schema, exposed as `ctx.dispatchInputs` (see [triggers → typed dispatch inputs](https://docs.kici.dev/user/sdk/triggers/#typed-dispatch-inputs)) |
|
|
5941
|
+
| `--files <path>` | git diff | Override changed file paths (repeatable, default: git diff) |
|
|
5942
|
+
| `--quiet` | `false` | Suppress streaming output (summary only) |
|
|
5943
|
+
| `--json` | `false` | Output structured JSON result |
|
|
5944
|
+
| `--junit <path>` | none | Output JUnit XML result to file |
|
|
5945
|
+
| `--debug` | `false` | Verbose internals |
|
|
5946
|
+
| `--kici-dir <path>` | `.kici` | Path to .kici directory |
|
|
5947
|
+
| `--in-place` | `false` | Run against the real working directory instead of an isolated tmp checkout (see "Execution isolation" below) |
|
|
5948
|
+
| `--keep` | `false` | Always retain the isolated tmp checkout (default: keep only on failure) |
|
|
5678
5949
|
|
|
5679
5950
|
**Interactive workflow selection (`--pick` / `-p`):**
|
|
5680
5951
|
|
|
@@ -5778,9 +6049,11 @@ kici run local push --keep-going
|
|
|
5778
6049
|
|
|
5779
6050
|
Execute fixtures remotely through the full CI pipeline. Fixtures are defined in `.kici/tests/*.ts` using the `fixture()` factory function. Without arguments, lists available fixtures.
|
|
5780
6051
|
|
|
5781
|
-
Remote runs route through the Platform. Authenticate with a personal access token (`kici login`), then target an organization with `kici org use <org>` or the `--org` flag. The Platform relays the run to the org's orchestrator, while your working-tree overlay uploads directly to object storage — see [How the run is routed](
|
|
6052
|
+
Remote runs route through the Platform. Authenticate with a personal access token (`kici login`), then target an organization with `kici org use <org>` or the `--org` flag. The Platform relays the run to the org's orchestrator, while your working-tree overlay uploads directly to object storage — see [How the run is routed](https://docs.kici.dev/user/cli-reference/#how-the-run-is-routed) and [The two planes](https://docs.kici.dev/user/cli-reference/#the-two-planes) below.
|
|
5782
6053
|
|
|
5783
|
-
|
|
6054
|
+
Like `kici run local`, `kici run remote` recompiles your workflows (`.kici/workflows` → `kici.lock.json`) before dispatching, so the orchestrator matches and dispatches against your current workflow definitions — a brand-new or edited workflow takes effect without a separate `kici compile`. A compile or validation error aborts the run before anything is uploaded.
|
|
6055
|
+
|
|
6056
|
+
The orchestrator must have **cache storage configured** (`KICI_STORAGE_TYPE` = `s3` or `filesystem`) with a dev-reachable upload endpoint so the CLI's direct upload succeeds; see the [testing guide](https://docs.kici.dev/user/testing-guide/) and [Storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/) for setup.
|
|
5784
6057
|
|
|
5785
6058
|
```bash
|
|
5786
6059
|
kici run remote [fixture] [options]
|
|
@@ -5794,24 +6067,26 @@ kici run remote [fixture] [options]
|
|
|
5794
6067
|
|
|
5795
6068
|
**Options:**
|
|
5796
6069
|
|
|
5797
|
-
| Option | Default | Description
|
|
5798
|
-
| --------------------------- | ------- |
|
|
5799
|
-
| `--org <id>` | active | Target organization for this run (overrides `kici org use`)
|
|
5800
|
-
| `--orchestrator <name>` | default | Target orchestrator cluster within the org (overrides the per-org default)
|
|
5801
|
-
| `--all` | `false` | Run all fixtures
|
|
5802
|
-
|
|
|
5803
|
-
| `--
|
|
5804
|
-
| `--
|
|
5805
|
-
| `--
|
|
5806
|
-
| `--
|
|
5807
|
-
| `--
|
|
5808
|
-
| `--
|
|
5809
|
-
| `--
|
|
5810
|
-
| `--
|
|
5811
|
-
| `--
|
|
5812
|
-
| `--
|
|
5813
|
-
| `--
|
|
5814
|
-
| `--
|
|
6070
|
+
| Option | Default | Description |
|
|
6071
|
+
| --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
6072
|
+
| `--org <id>` | active | Target organization for this run (overrides `kici org use`) |
|
|
6073
|
+
| `--orchestrator <name>` | default | Target orchestrator cluster within the org (overrides the per-org default) |
|
|
6074
|
+
| `--all` | `false` | Run all fixtures |
|
|
6075
|
+
| `-p, --pick` | `false` | Interactively pick fixtures to run (multi-select; mutex with a fixture arg, `--all`, and `--workflow`) |
|
|
6076
|
+
| `--workflow <name>` | none | Run a specific workflow directly (bypass triggers) |
|
|
6077
|
+
| `--parallel` | `false` | Run multiple fixtures concurrently |
|
|
6078
|
+
| `--no-wait` | - | Fire and forget (print runIds, don't stream) |
|
|
6079
|
+
| `--quiet` | `false` | Minimal output (only final result) |
|
|
6080
|
+
| `--json` | `false` | Machine-readable JSON output |
|
|
6081
|
+
| `--junit <path>` | none | JUnit XML output to file for CI integration |
|
|
6082
|
+
| `--history` | `false` | Show table of recent test runs |
|
|
6083
|
+
| `--context <ctx.key=value>` | none | Inject a namespaced context secret, uploaded encrypted (repeatable) |
|
|
6084
|
+
| `--env <KEY=VALUE>` | none | Provide a per-run secret, uploaded encrypted (repeatable) — see [testing guide](https://docs.kici.dev/user/testing-guide/) |
|
|
6085
|
+
| `--input <KEY=VALUE>` | none | Typed workflow-dispatch input (repeatable) — validated + coerced + defaulted on the orchestrator from the lock descriptor, exposed as `ctx.dispatchInputs` (see [triggers → typed dispatch inputs](https://docs.kici.dev/user/sdk/triggers/#typed-dispatch-inputs)) |
|
|
6086
|
+
| `--target <selector>` | none | Narrow `runsOnAll` jobs to hosts matching this label selector (repeatable, AND-combined) |
|
|
6087
|
+
| `--target-allow-empty` | `false` | A `--target` that narrows a `runsOnAll` job to zero hosts skips it instead of failing |
|
|
6088
|
+
| `--debug` | `false` | Verbose internals |
|
|
6089
|
+
| `--kici-dir <path>` | `.kici` | Path to .kici directory |
|
|
5815
6090
|
|
|
5816
6091
|
**Examples:**
|
|
5817
6092
|
|
|
@@ -5849,6 +6124,9 @@ kici run remote push-main --no-wait
|
|
|
5849
6124
|
# View recent test run history
|
|
5850
6125
|
kici run remote --history
|
|
5851
6126
|
|
|
6127
|
+
# Interactively pick which fixtures to run (multi-select)
|
|
6128
|
+
kici run remote --pick
|
|
6129
|
+
|
|
5852
6130
|
# Narrow runsOnAll jobs to a subset of the host roster
|
|
5853
6131
|
kici run remote deploy --target role:web
|
|
5854
6132
|
|
|
@@ -5859,6 +6137,18 @@ kici run remote deploy --target role:web --target dc:eu
|
|
|
5859
6137
|
kici run remote deploy --target role:gpu --target-allow-empty
|
|
5860
6138
|
```
|
|
5861
6139
|
|
|
6140
|
+
**Interactive fixture selection (`--pick` / `-p`):**
|
|
6141
|
+
|
|
6142
|
+
Pass `--pick` (or `-p`) to open an interactive checkbox menu of the available
|
|
6143
|
+
fixtures. Toggle one or more with space, confirm with enter, and the selected
|
|
6144
|
+
fixtures run through the normal remote pipeline (honoring `--parallel`,
|
|
6145
|
+
`--no-wait`, and the other run flags). Notes:
|
|
6146
|
+
|
|
6147
|
+
- `--pick` is mutually exclusive with a fixture argument, `--all`, and
|
|
6148
|
+
`--workflow`. Passing any together exits with code 2.
|
|
6149
|
+
- When `stdin` is not a TTY, `--pick` prints the available fixtures and exits
|
|
6150
|
+
without running anything — pass a fixture name (or `--all`) in scripts.
|
|
6151
|
+
|
|
5862
6152
|
#### Host narrowing with `--target`
|
|
5863
6153
|
|
|
5864
6154
|
`--target <selector>` is a runtime narrowing for `runsOnAll` jobs, analogous to
|
|
@@ -5883,7 +6173,7 @@ When `--target` narrows a `runsOnAll` job to zero hosts, the default is to **fai
|
|
|
5883
6173
|
the run (fail-loud — a typo in the selector shouldn't silently skip work). Pass
|
|
5884
6174
|
`--target-allow-empty` to **skip** the zeroed job instead; the job records a
|
|
5885
6175
|
`skipped` status, and any downstream job that needs it with `when: 'on-skip'` (or
|
|
5886
|
-
`when: 'always'`) still runs. See [Job dependencies](
|
|
6176
|
+
`when: 'always'`) still runs. See [Job dependencies](https://docs.kici.dev/user/sdk/core/#job-dependencies-needs)
|
|
5887
6177
|
for the `when` gating model.
|
|
5888
6178
|
|
|
5889
6179
|
**Exit codes:**
|
|
@@ -5915,9 +6205,9 @@ When an org has more than one connected orchestrator cluster, the CLI picks the
|
|
|
5915
6205
|
`kici run remote` uses two independent paths:
|
|
5916
6206
|
|
|
5917
6207
|
- **Control plane** — run initiation, trigger, status, log retrieval, and cancellation flow from your machine through the Platform, which relays them over a WebSocket connection to the org's orchestrator. Logs are delivered by the CLI polling the Platform for log chunks (tracked by a monotonic line cursor) and run status until the run reaches a terminal state; there is no direct streaming socket to the orchestrator.
|
|
5918
|
-
- **Data plane** — your working-tree overlay tarball uploads **directly** from your machine to the orchestrator's object store via a presigned PUT URL. The overlay never passes through the Platform. This is why the orchestrator's object-store upload endpoint must be reachable from your machine; see [Storage layout](
|
|
6208
|
+
- **Data plane** — your working-tree overlay tarball uploads **directly** from your machine to the orchestrator's object store via a presigned PUT URL. The overlay never passes through the Platform. This is why the orchestrator's object-store upload endpoint must be reachable from your machine; see [Storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/).
|
|
5919
6209
|
|
|
5920
|
-
An orchestrator with no Platform connection cannot serve remote runs — the Platform is the service that offers them. For executing workflow steps on your own machine without an orchestrator (no scaler, agents, or environments), use [`kici run local`](
|
|
6210
|
+
An orchestrator with no Platform connection cannot serve remote runs — the Platform is the service that offers them. For executing workflow steps on your own machine without an orchestrator (no scaler, agents, or environments), use [`kici run local`](https://docs.kici.dev/user/cli-reference/#kici-run-local).
|
|
5921
6211
|
|
|
5922
6212
|
#### Fresh repos (no GitHub remote)
|
|
5923
6213
|
|
|
@@ -5931,7 +6221,7 @@ An orchestrator with no Platform connection cannot serve remote runs — the Pla
|
|
|
5931
6221
|
|
|
5932
6222
|
Destination routing is unchanged for fresh repos: the run still goes to your active org through the Platform.
|
|
5933
6223
|
|
|
5934
|
-
For a detailed guide on writing fixtures, configuring secrets, and understanding the upload flow, see [Testing guide](testing-guide
|
|
6224
|
+
For a detailed guide on writing fixtures, configuring secrets, and understanding the upload flow, see [Testing guide](https://docs.kici.dev/user/testing-guide/).
|
|
5935
6225
|
|
|
5936
6226
|
#### kici orchestrators
|
|
5937
6227
|
|
|
@@ -6316,7 +6606,7 @@ for `--quiet`.
|
|
|
6316
6606
|
|
|
6317
6607
|
### kici approve
|
|
6318
6608
|
|
|
6319
|
-
Approve a held [approval gate](approvals
|
|
6609
|
+
Approve a held [approval gate](https://docs.kici.dev/user/approvals/) so the run resumes. Identify the held element by run ID, optionally narrowed to a job and step.
|
|
6320
6610
|
|
|
6321
6611
|
```bash
|
|
6322
6612
|
kici approve <run-id> [options]
|
|
@@ -6352,7 +6642,7 @@ You must be eligible for at least one unsatisfied clause (a member of a named te
|
|
|
6352
6642
|
|
|
6353
6643
|
### kici reject
|
|
6354
6644
|
|
|
6355
|
-
Reject a held [approval gate](approvals
|
|
6645
|
+
Reject a held [approval gate](https://docs.kici.dev/user/approvals/). A rejection fails the held element and the run. A reason is required.
|
|
6356
6646
|
|
|
6357
6647
|
```bash
|
|
6358
6648
|
kici reject <run-id> --reason <text> [options]
|
|
@@ -6431,7 +6721,7 @@ After generating types, `ctx.secrets.get('MY_KEY')` and `ctx.secrets.expose('DB_
|
|
|
6431
6721
|
|
|
6432
6722
|
**Git workflow:** Commit the generated `.kici/types/secrets.d.ts` so team members get type checking without needing orchestrator access. Run `kici types` to refresh when environments change.
|
|
6433
6723
|
|
|
6434
|
-
**Auto-regeneration:** `kici compile` automatically runs `kici types` after successful compilation when authenticated. See the [kici compile](
|
|
6724
|
+
**Auto-regeneration:** `kici compile` automatically runs `kici types` after successful compilation when authenticated. See the [kici compile](https://docs.kici.dev/user/cli-reference/#kici-compile) section for details.
|
|
6435
6725
|
|
|
6436
6726
|
**Escape hatch:** For dynamic keys not in the generated types, use a cast: `(ctx.secrets as any).DYNAMIC_KEY`.
|
|
6437
6727
|
|
|
@@ -6650,9 +6940,10 @@ Open the KiCI documentation site in the default browser. With the `llm` subcomma
|
|
|
6650
6940
|
```bash
|
|
6651
6941
|
kici docs # open https://kici.dev/docs/
|
|
6652
6942
|
kici docs --no-open # print the URL instead of opening a browser
|
|
6653
|
-
kici docs llm # print llms
|
|
6654
|
-
kici docs llm
|
|
6655
|
-
kici docs llm
|
|
6943
|
+
kici docs llm # print the llms.txt index (a router over the task bundles)
|
|
6944
|
+
kici docs llm sdk # print the SDK task bundle
|
|
6945
|
+
kici docs llm full # print llms-full.txt (every page in one file)
|
|
6946
|
+
kici docs llm sdk --out sdk-context.md # write a bundle to a file
|
|
6656
6947
|
```
|
|
6657
6948
|
|
|
6658
6949
|
**Examples:**
|
|
@@ -6661,14 +6952,14 @@ kici docs llm --out path/to/file.md # write the bundle to a file
|
|
|
6661
6952
|
# Open the docs site in your browser
|
|
6662
6953
|
kici docs
|
|
6663
6954
|
|
|
6664
|
-
# Pipe the
|
|
6665
|
-
kici docs llm | claude -- "Read this and help me author a deploy workflow"
|
|
6955
|
+
# Pipe just the SDK bundle into a coding agent (small, task-scoped context)
|
|
6956
|
+
kici docs llm sdk | claude -- "Read this and help me author a deploy workflow"
|
|
6666
6957
|
|
|
6667
|
-
# Save the
|
|
6668
|
-
kici docs llm --
|
|
6958
|
+
# Save the router index for offline reference
|
|
6959
|
+
kici docs llm --out kici-llms-index.txt
|
|
6669
6960
|
```
|
|
6670
6961
|
|
|
6671
|
-
|
|
6962
|
+
Bundles are regenerated from `docs/` every time `@kici-dev/compiler` is built, so they always match your installed CLI version. The index lists each task bundle — `getting-started`, `sdk`, `cli`, `patterns`, `features`, `providers`, `architecture` — with its size and a one-line purpose; pass the bundle id as the topic. Every cross-reference link inside a bundle is an absolute `docs.kici.dev` URL. The same files are published online following the [llms.txt convention](https://llmstxt.org/).
|
|
6672
6963
|
|
|
6673
6964
|
### kici admin
|
|
6674
6965
|
|
|
@@ -6707,7 +6998,7 @@ kici admin drain-worker --url http://worker-2.internal:10143
|
|
|
6707
6998
|
|
|
6708
6999
|
### kici verify-attestation
|
|
6709
7000
|
|
|
6710
|
-
Verify a KiCI build-provenance attestation bundle offline. A bundle is the signed package a workflow step produces via `ctx.attestProvenance(...)`: a DSSE-wrapped SLSA in-toto statement, the ephemeral public key that signed it, and the KiCI identity token that anchors the build context. For the end-to-end attest → verify → view journey, see the [build provenance guide](
|
|
7001
|
+
Verify a KiCI build-provenance attestation bundle offline. A bundle is the signed package a workflow step produces via `ctx.attestProvenance(...)`: a DSSE-wrapped SLSA in-toto statement, the ephemeral public key that signed it, and the KiCI identity token that anchors the build context. For the end-to-end attest → verify → view journey, see the [build provenance guide](https://docs.kici.dev/user/provenance/). Verification establishes the full chain — the identity token verifies against the trusted issuer's JWKS, the DSSE signature verifies against the bundled key, and the statement's build context must match the token's claims (a mismatch is a hard failure). When an `[artifact]` is given, its SHA-256 digest is also matched against the attestation subject.
|
|
6711
7002
|
|
|
6712
7003
|
```bash
|
|
6713
7004
|
kici verify-attestation [artifact] --bundle <path-or-url> --trust-root <url-or-file> [options]
|
|
@@ -6796,7 +7087,7 @@ The `kici compile` command produces `.kici/kici.lock.json` inside the `.kici` di
|
|
|
6796
7087
|
- Should be committed to version control
|
|
6797
7088
|
- Is regenerated on every `kici compile` run
|
|
6798
7089
|
|
|
6799
|
-
Use `kici compile --check` in CI to validate that workflows are correct without writing files. For the full story on drift, pre-commit/CI, and agent-side verification, see [Lock file and workflow drift](lock-file-and-drift
|
|
7090
|
+
Use `kici compile --check` in CI to validate that workflows are correct without writing files. For the full story on drift, pre-commit/CI, and agent-side verification, see [Lock file and workflow drift](https://docs.kici.dev/user/lock-file-and-drift/).
|
|
6800
7091
|
|
|
6801
7092
|
## Exit codes
|
|
6802
7093
|
|
|
@@ -6834,10 +7125,10 @@ Set `KICI_DEBUG=true` for additional internal debug output across all commands.
|
|
|
6834
7125
|
|
|
6835
7126
|
## See also
|
|
6836
7127
|
|
|
6837
|
-
- [Getting started](getting-started
|
|
6838
|
-
- [Testing guide](testing-guide
|
|
6839
|
-
- [SDK reference](sdk-reference
|
|
6840
|
-
- [Workflow patterns](workflow-patterns
|
|
7128
|
+
- [Getting started](https://docs.kici.dev/user/getting-started/) -- install the SDK and write your first workflow
|
|
7129
|
+
- [Testing guide](https://docs.kici.dev/user/testing-guide/) -- writing fixtures, remote test runs, secret contexts, and repo state transfer
|
|
7130
|
+
- [SDK reference](https://docs.kici.dev/user/sdk-reference/) -- complete API for the workflow definitions that the CLI compiles
|
|
7131
|
+
- [Workflow patterns](https://docs.kici.dev/user/workflow-patterns/) -- example workflows to compile and test with these commands
|
|
6841
7132
|
|
|
6842
7133
|
---
|
|
6843
7134
|
|
|
@@ -7118,12 +7409,12 @@ Each workflow entry includes:
|
|
|
7118
7409
|
| `jobs` | Job definitions with scheduling metadata (runsOn, needs, matrix, environment, concurrency, container, checkout, gracePeriod, label routing, dynamic fields, etc.). |
|
|
7119
7410
|
| `rules` | Workflow-level conditional rules (optional). Stored as dynamic references since rule functions cannot be serialized. |
|
|
7120
7411
|
| `description` | Optional workflow description. |
|
|
7121
|
-
| `hashFiles` | Declared glob patterns for extra files included in the content hash (optional). See [extra files in the content hash](
|
|
7412
|
+
| `hashFiles` | Declared glob patterns for extra files included in the content hash (optional). See [extra files in the content hash](https://docs.kici.dev/user/lock-file-and-drift/#extra-files-in-the-content-hash-hashfiles). |
|
|
7122
7413
|
| `resolvedHashFiles` | Resolved file paths from `hashFiles` at compile time (optional). Recorded so the agent can verify without re-discovering. |
|
|
7123
7414
|
| `contexts` | Secret contexts declared by the workflow (optional). The orchestrator validates access to each context before dispatch. |
|
|
7124
|
-
| `registries` | Private npm registry declarations the agent authenticates against before install (optional): `url`, `scope`, `tokenSecret` reference, `alwaysAuth`. Resolved token bytes never appear in the lock file. See [private registries](private-registries
|
|
7125
|
-
| `installEnv` | Extra qualified secret refs (`<environment>:<secret-name>`) projected as env vars on the install subprocess for use with a committed `.kici/.npmrc` (optional). See [private registries](private-registries
|
|
7126
|
-
| `concurrency` | Workflow-level concurrency config: `hasGroup`, `cancelInProgress`, `max` (optional). See [concurrency groups](concurrency
|
|
7415
|
+
| `registries` | Private npm registry declarations the agent authenticates against before install (optional): `url`, `scope`, `tokenSecret` reference, `alwaysAuth`. Resolved token bytes never appear in the lock file. See [private registries](https://docs.kici.dev/user/private-registries/). |
|
|
7416
|
+
| `installEnv` | Extra qualified secret refs (`<environment>:<secret-name>`) projected as env vars on the install subprocess for use with a committed `.kici/.npmrc` (optional). See [private registries](https://docs.kici.dev/user/private-registries/). |
|
|
7417
|
+
| `concurrency` | Workflow-level concurrency config: `hasGroup`, `cancelInProgress`, `max` (optional). See [concurrency groups](https://docs.kici.dev/user/concurrency/). |
|
|
7127
7418
|
| `timeout` | Whole-run wall-clock timeout in milliseconds (optional). The orchestrator reads this at run creation to set the run deadline. |
|
|
7128
7419
|
| Hook flags | Boolean flags (`hasOnCancel`, `hasCleanup`, `hasOnSuccess`, `hasOnFailure`) indicating which lifecycle hooks are defined. Job entries additionally have `hasBeforeStep` and `hasAfterStep`. |
|
|
7129
7420
|
|
|
@@ -7152,7 +7443,7 @@ Install a hook that compiles and stages the lock file before each commit:
|
|
|
7152
7443
|
npx kici hook install
|
|
7153
7444
|
```
|
|
7154
7445
|
|
|
7155
|
-
This runs `kici compile && git add .kici/kici.lock.json` before each commit: if compilation fails the commit is blocked; if it succeeds the updated lock file is automatically staged. See [CLI Reference — kici hook](cli-reference
|
|
7446
|
+
This runs `kici compile && git add .kici/kici.lock.json` before each commit: if compilation fails the commit is blocked; if it succeeds the updated lock file is automatically staged. See [CLI Reference — kici hook](https://docs.kici.dev/user/cli-reference/#kici-hook) for options (husky, lefthook, pre-commit, prek, raw git).
|
|
7156
7447
|
|
|
7157
7448
|
### CI check
|
|
7158
7449
|
|
|
@@ -7202,9 +7493,9 @@ So even without a pre-commit or CI check, a stale lock file will cause the run t
|
|
|
7202
7493
|
|
|
7203
7494
|
## See also
|
|
7204
7495
|
|
|
7205
|
-
- [Getting Started](getting-started
|
|
7206
|
-
- [CLI Reference](cli-reference
|
|
7207
|
-
- [Architecture — Data flows](
|
|
7496
|
+
- [Getting Started](https://docs.kici.dev/user/getting-started/) — compile and commit the lock file
|
|
7497
|
+
- [CLI Reference](https://docs.kici.dev/user/cli-reference/) — `kici compile`, `kici compile --check`, `kici hook`
|
|
7498
|
+
- [Architecture — Data flows](https://docs.kici.dev/architecture/data-flows/) — how the lock file is used in the pipeline
|
|
7208
7499
|
|
|
7209
7500
|
---
|
|
7210
7501
|
|
|
@@ -7226,14 +7517,14 @@ Test your workflows remotely against the full CI pipeline from your local machin
|
|
|
7226
7517
|
The command is remote-only -- all execution happens on the orchestrator and agent. For local-only trigger matching previews, use `kici test <event>`.
|
|
7227
7518
|
|
|
7228
7519
|
:::note[Orchestrator prerequisite: cache storage]
|
|
7229
|
-
`kici run remote` uploads your working-tree overlay to the orchestrator's **cache storage** via a pre-signed URL, and the agent fetches it from there (see [Repo state transfer](
|
|
7520
|
+
`kici run remote` uploads your working-tree overlay to the orchestrator's **cache storage** via a pre-signed URL, and the agent fetches it from there (see [Repo state transfer](https://docs.kici.dev/user/testing-guide/#repo-state-transfer)). The target orchestrator must therefore have cache storage enabled (`KICI_STORAGE_TYPE` = `s3` or `filesystem`).
|
|
7230
7521
|
|
|
7231
|
-
- **Both quickstarts wire this up for you** — the [Docker / Podman quickstart](quickstart/compose
|
|
7522
|
+
- **Both quickstarts wire this up for you** — the [Docker / Podman quickstart](https://docs.kici.dev/user/quickstart/compose/) and the [bare-metal quickstart](https://docs.kici.dev/user/quickstart/bare-metal/) each ship a SeaweedFS object store and pre-fill the orchestrator's `KICI_STORAGE_*` block, so `kici run remote` works out of the box (see each guide's "run a workflow without pushing" step).
|
|
7232
7523
|
- **A hand-rolled orchestrator deploy does not configure storage by default** — enable a backend before using `kici run remote`:
|
|
7233
7524
|
- **`filesystem`** — simplest for a single-host orchestrator: set `KICI_STORAGE_TYPE=filesystem` and `KICI_STORAGE_FS_PATH=/var/lib/kici/cache`. No external service needed; blobs are served through the orchestrator's own HMAC-signed HTTP route.
|
|
7234
7525
|
- **`s3`** — any S3-compatible bucket. **A non-public / self-hosted endpoint works**: set `KICI_STORAGE_TYPE=s3`, `KICI_STORAGE_BUCKET`, `KICI_STORAGE_ENDPOINT=https://your-endpoint` and (for most self-hosted services) `KICI_STORAGE_FORCE_PATH_STYLE=true`. If the developer machine running `kici run remote` reaches the bucket at a different address than the orchestrator, set `KICI_STORAGE_UPLOAD_ENDPOINT` to the developer-reachable address; if agents reach it at yet another address (e.g. agents in containers), set `KICI_STORAGE_EXTERNAL_ENDPOINT` to the agent-routable URL.
|
|
7235
7526
|
|
|
7236
|
-
See [Storage layout](
|
|
7527
|
+
See [Storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/) for the full env-var reference.
|
|
7237
7528
|
:::
|
|
7238
7529
|
|
|
7239
7530
|
## Getting started
|
|
@@ -7244,7 +7535,7 @@ See [Storage layout](../operator/orchestrator/storage-layout.md) for the full en
|
|
|
7244
7535
|
kici login
|
|
7245
7536
|
```
|
|
7246
7537
|
|
|
7247
|
-
This opens your browser for OAuth authentication and stores a personal access token in `~/.kici/config`. For CI/CD pipelines or headless environments, use `kici login --token <your-api-key>` or `kici login --device` instead. See [CLI authentication](cli-auth
|
|
7538
|
+
This opens your browser for OAuth authentication and stores a personal access token in `~/.kici/config`. For CI/CD pipelines or headless environments, use `kici login --token <your-api-key>` or `kici login --device` instead. See [CLI authentication](https://docs.kici.dev/user/cli-auth/) for details.
|
|
7248
7539
|
|
|
7249
7540
|
### 2. Write a test fixture
|
|
7250
7541
|
|
|
@@ -7352,8 +7643,8 @@ This maps the `db` secret context to the `test-database` context, and `api` to `
|
|
|
7352
7643
|
|
|
7353
7644
|
This mapping is honored by **both** `kici run local` and `kici run remote`:
|
|
7354
7645
|
|
|
7355
|
-
- For **`kici run local`** (see [`kici run local`](cli-reference
|
|
7356
|
-
- For **`kici run remote`**, each named context maps to an orchestrator **environment**, and the orchestrator resolves that environment's secrets for the run. The target environment must be flagged `allowLocalExecution: true` — mapping a context to a missing or non-test environment rejects the run (see [Secret contexts for testing](
|
|
7646
|
+
- For **`kici run local`** (see [`kici run local`](https://docs.kici.dev/user/cli-reference/#kici-run-local)), each named context is resolved from your local secret files (`.kici/.secrets`, `.env.local`, `secrets.yaml`, and `--env` flags).
|
|
7647
|
+
- For **`kici run remote`**, each named context maps to an orchestrator **environment**, and the orchestrator resolves that environment's secrets for the run. The target environment must be flagged `allowLocalExecution: true` — mapping a context to a missing or non-test environment rejects the run (see [Secret contexts for testing](https://docs.kici.dev/user/testing-guide/#secret-contexts-for-testing) below).
|
|
7357
7648
|
|
|
7358
7649
|
### Async fixtures
|
|
7359
7650
|
|
|
@@ -7508,7 +7799,7 @@ Because these values originate on your machine, they are the natural place to pu
|
|
|
7508
7799
|
|
|
7509
7800
|
In addition to your uploaded values, the orchestrator resolves test-scoped secrets from its own store for a remote test run:
|
|
7510
7801
|
|
|
7511
|
-
- The job's own declared `environment` contributes its resolved secrets (flat). Static strings and **pure dynamic functions** both participate: a pure `environment:` function (see [Dynamic values](dynamic-values
|
|
7802
|
+
- The job's own declared `environment` contributes its resolved secrets (flat). Static strings and **pure dynamic functions** both participate: a pure `environment:` function (see [Dynamic values](https://docs.kici.dev/user/dynamic-values/)) is evaluated against the fixture's simulated event, and the resolved name is gated and resolved like a static one. Impure dynamic functions (those requiring an init job) are not evaluated for test runs — use a fixture `secrets:` mapping (or `--context`) to supply such a job's secrets.
|
|
7512
7803
|
- Each fixture `secrets: { ctx: envName }` mapping resolves the named environment's secrets under the namespaced context `ctx`.
|
|
7513
7804
|
|
|
7514
7805
|
Both paths are restricted to environments flagged `allowLocalExecution: true`. A production environment left at the default `false` is never resolvable for a test run.
|
|
@@ -7553,7 +7844,7 @@ or via the dashboard's "Test runs" toggle on the environment detail page. `kici
|
|
|
7553
7844
|
|
|
7554
7845
|
### Local execution as an alternative
|
|
7555
7846
|
|
|
7556
|
-
`kici run local` resolves the same local secret files entirely on your machine and honors the fixture `secrets: { ... }` mapping to pick which local context backs each name (see [`kici run local`](cli-reference
|
|
7847
|
+
`kici run local` resolves the same local secret files entirely on your machine and honors the fixture `secrets: { ... }` mapping to pick which local context backs each name (see [`kici run local`](https://docs.kici.dev/user/cli-reference/#kici-run-local)). Because the values never leave your machine, it's a good fit when you want to exercise secret-dependent steps without involving the orchestrator at all.
|
|
7557
7848
|
|
|
7558
7849
|
### Discovering available contexts
|
|
7559
7850
|
|
|
@@ -7630,9 +7921,9 @@ export const pushMain = fixture('push-main', {
|
|
|
7630
7921
|
|
|
7631
7922
|
## See also
|
|
7632
7923
|
|
|
7633
|
-
- [CLI reference](cli-reference
|
|
7634
|
-
- [SDK reference](sdk-reference
|
|
7635
|
-
- [Workflow patterns](workflow-patterns
|
|
7924
|
+
- [CLI reference](https://docs.kici.dev/user/cli-reference/) -- complete command reference for all `kici` commands
|
|
7925
|
+
- [SDK reference](https://docs.kici.dev/user/sdk-reference/) -- trigger functions, step context, and workflow API
|
|
7926
|
+
- [Workflow patterns](https://docs.kici.dev/user/workflow-patterns/) -- example workflows to test against
|
|
7636
7927
|
|
|
7637
7928
|
---
|
|
7638
7929
|
|
|
@@ -7640,24 +7931,24 @@ export const pushMain = fixture('push-main', {
|
|
|
7640
7931
|
|
|
7641
7932
|
Source: https://docs.kici.dev/user/workflow-patterns/
|
|
7642
7933
|
|
|
7643
|
-
Practical patterns for building real-world KiCI workflows in TypeScript. The patterns are organised across five pages -- start with [Basic CI](
|
|
7934
|
+
Practical patterns for building real-world KiCI workflows in TypeScript. The patterns are organised across five pages -- start with [Basic CI](https://docs.kici.dev/user/patterns/basic/) if you're new, or jump to [Integrations](https://docs.kici.dev/user/patterns/integrations/) if you're wiring up a non-GitHub forge or a generic webhook.
|
|
7644
7935
|
|
|
7645
7936
|
| Page | Covers |
|
|
7646
7937
|
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
7647
|
-
| [Basic CI](
|
|
7648
|
-
| [Conditionals & matrix](
|
|
7649
|
-
| [Integrations](
|
|
7650
|
-
| [Scheduling & events](
|
|
7651
|
-
| [Pattern reference](
|
|
7938
|
+
| [Basic CI](https://docs.kici.dev/user/patterns/basic/) | Single-job CI, PR-only / push-only filters, multiple triggers on one workflow, manual-only workflows. |
|
|
7939
|
+
| [Conditionals & matrix](https://docs.kici.dev/user/patterns/conditionals-matrix/) | Conditional execution with rules, matrix builds (static + dynamic), and dynamic job generation. |
|
|
7940
|
+
| [Integrations](https://docs.kici.dev/user/patterns/integrations/) | Workflow chaining, generic webhooks, Stripe handlers, self-hosted git forges (Forgejo / Gitea / Gogs), plain GitHub repo webhooks (no GitHub App). |
|
|
7941
|
+
| [Scheduling & events](https://docs.kici.dev/user/patterns/scheduling-and-events/) | Nightly cron, workflow-complete-triggered deploys, custom event chaining. |
|
|
7942
|
+
| [Pattern reference](https://docs.kici.dev/user/patterns/reference/) | Step context, the examples repository, and GitHub check run output -- cross-cutting reference shared by every pattern above. |
|
|
7652
7943
|
|
|
7653
7944
|
## See also
|
|
7654
7945
|
|
|
7655
|
-
- [Event system](events
|
|
7656
|
-
- [SDK reference](sdk-reference
|
|
7657
|
-
- [CLI reference](cli-reference
|
|
7658
|
-
- [Getting started](getting-started
|
|
7659
|
-
- [Job execution lifecycle](
|
|
7660
|
-
- [GitHub checks architecture](
|
|
7946
|
+
- [Event system](https://docs.kici.dev/user/events/) -- event model concepts, registration model, circuit breaker
|
|
7947
|
+
- [SDK reference](https://docs.kici.dev/user/sdk-reference/) -- complete API reference for all functions used in these patterns
|
|
7948
|
+
- [CLI reference](https://docs.kici.dev/user/cli-reference/) -- how to compile and test these workflows locally
|
|
7949
|
+
- [Getting started](https://docs.kici.dev/user/getting-started/) -- installation and first workflow setup
|
|
7950
|
+
- [Job execution lifecycle](https://docs.kici.dev/architecture/execution/job-execution/) -- how agents execute the jobs defined in these patterns
|
|
7951
|
+
- [GitHub checks architecture](https://docs.kici.dev/architecture/webhooks/github-checks/) -- deep dive into the check run system
|
|
7661
7952
|
|
|
7662
7953
|
---
|
|
7663
7954
|
|
|
@@ -7677,7 +7968,7 @@ your organizations, roles, and API keys stay attached to the same identity.
|
|
|
7677
7968
|
Sign-in methods and passwords are managed in your **account console**, provided
|
|
7678
7969
|
by the identity provider that handles single sign-on for KiCI. The dashboard's
|
|
7679
7970
|
**Linked accounts** page does not control how you sign in — see
|
|
7680
|
-
[Linked accounts vs sign-in methods](
|
|
7971
|
+
[Linked accounts vs sign-in methods](https://docs.kici.dev/user/account-and-login/#linked-accounts-vs-sign-in-methods) below.
|
|
7681
7972
|
|
|
7682
7973
|
You can open the account console from the dashboard: go to your personal
|
|
7683
7974
|
settings, open **Linked accounts**, and use the **Account console** link.
|
|
@@ -7727,9 +8018,9 @@ You declare a gate in your workflow with `approval`. It is available at three le
|
|
|
7727
8018
|
- **Job** — hold the job before any of its steps run.
|
|
7728
8019
|
- **Workflow** — hold the whole run before any job is dispatched.
|
|
7729
8020
|
|
|
7730
|
-
A step-level gate can also fire **only when a check/apply step finds drift** — Terraform's plan→apply, per step. See [Drift gates](
|
|
8021
|
+
A step-level gate can also fire **only when a check/apply step finds drift** — Terraform's plan→apply, per step. See [Drift gates](https://docs.kici.dev/user/approvals/#drift-gates-whendrift) below.
|
|
7731
8022
|
|
|
7732
|
-
Approvers are named as **teams** and **users**. A team is an operator-defined group of org members; your workflow code may name a team but can never change its membership, which is what makes a team clause a real gate rather than a suggestion. See [Approval gates (operator guide)](
|
|
8023
|
+
Approvers are named as **teams** and **users**. A team is an operator-defined group of org members; your workflow code may name a team but can never change its membership, which is what makes a team clause a real gate rather than a suggestion. See [Approval gates (operator guide)](https://docs.kici.dev/operator/approvals/) for how operators define teams, the approval queue, and expiry; see [the architecture overview](https://docs.kici.dev/architecture/approvals/) for how a hold is evaluated and resumed.
|
|
7733
8024
|
|
|
7734
8025
|
## Quick start
|
|
7735
8026
|
|
|
@@ -7750,7 +8041,7 @@ export default workflow('deploy', {
|
|
|
7750
8041
|
});
|
|
7751
8042
|
```
|
|
7752
8043
|
|
|
7753
|
-
When the run reaches this job, it is held instead of dispatched. The held run appears in the dashboard approval queue and can be released from there or with the [`kici approve`](
|
|
8044
|
+
When the run reaches this job, it is held instead of dispatched. The held run appears in the dashboard approval queue and can be released from there or with the [`kici approve`](https://docs.kici.dev/user/approvals/#approving-from-the-cli) command. Once a member of `leads` approves, the job dispatches normally.
|
|
7754
8045
|
|
|
7755
8046
|
## The `approval` field
|
|
7756
8047
|
|
|
@@ -7798,12 +8089,12 @@ approval: {
|
|
|
7798
8089
|
|
|
7799
8090
|
| Field | Type | Description |
|
|
7800
8091
|
| ----------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
7801
|
-
| `when` | `'always' \| 'drift'` | When the gate fires. `'always'` (default) gates before the element; `'drift'` gates a check/apply step only when it finds drift. See [Drift gates](
|
|
8092
|
+
| `when` | `'always' \| 'drift'` | When the gate fires. `'always'` (default) gates before the element; `'drift'` gates a check/apply step only when it finds drift. See [Drift gates](https://docs.kici.dev/user/approvals/#drift-gates-whendrift). |
|
|
7802
8093
|
| `approvers` | `ApproverClause[]` | The AND list of `{ team }` / `{ user }` clauses. An empty list means "any approval-capable member". |
|
|
7803
8094
|
| `reason` | `string` | A human-readable label shown in the dashboard queue and the held-for-approval status check. |
|
|
7804
8095
|
| `timeout` | `number` | Per-gate expiry in **seconds**, overriding the org default. On expiry the element is rejected. |
|
|
7805
8096
|
|
|
7806
|
-
When `timeout` is omitted, the gate uses the org's default approval expiry (set by the operator). On expiry, the held element is rejected and the run fails — see [expiry](
|
|
8097
|
+
When `timeout` is omitted, the gate uses the org's default approval expiry (set by the operator). On expiry, the held element is rejected and the run fails — see [expiry](https://docs.kici.dev/operator/approvals/#expiry).
|
|
7807
8098
|
|
|
7808
8099
|
## Granularity
|
|
7809
8100
|
|
|
@@ -7855,13 +8146,13 @@ job('migrate-and-deploy', {
|
|
|
7855
8146
|
|
|
7856
8147
|
Here `build-plan` runs, then the job pauses for a `dba` approval. On approval, `apply-migration` runs against the exact workspace `build-plan` produced, followed by `deploy`. A rejection or expiry fails the job.
|
|
7857
8148
|
|
|
7858
|
-
Because a step-level hold keeps an agent and its workspace occupied for the whole human wait, prefer job- or workflow-level gates when you do not need prior-step state, and keep step-level timeouts short. See the [operator note on agent occupancy](
|
|
8149
|
+
Because a step-level hold keeps an agent and its workspace occupied for the whole human wait, prefer job- or workflow-level gates when you do not need prior-step state, and keep step-level timeouts short. See the [operator note on agent occupancy](https://docs.kici.dev/operator/approvals/#agent-occupancy-during-step-level-holds).
|
|
7859
8150
|
|
|
7860
8151
|
## Drift gates (`when: 'drift'`)
|
|
7861
8152
|
|
|
7862
|
-
A `when: 'drift'` gate is **step-scope only** and requires a [check/apply step](idempotent-steps
|
|
8153
|
+
A `when: 'drift'` gate is **step-scope only** and requires a [check/apply step](https://docs.kici.dev/user/idempotent-steps/). Instead of pausing unconditionally, it fires **between the step's `check` and `run`, only when `check` finds drift in apply mode** — exactly Terraform's plan→apply, scoped to one step. When the step is already in sync (no drift), nothing pauses and the step skips.
|
|
7863
8154
|
|
|
7864
|
-
When the gate fires, the held run carries the **computed drift** as a payload: the rendering your `summarize(drift)` produced (the per-file diff, the commands that would run), plus the structured drift. The dashboard approval queue and the [CLI](
|
|
8155
|
+
When the gate fires, the held run carries the **computed drift** as a payload: the rendering your `summarize(drift)` produced (the per-file diff, the commands that would run), plus the structured drift. The dashboard approval queue and the [CLI](https://docs.kici.dev/user/approvals/#approving-from-the-cli) show the actual diff the operator is approving — not a static reason string.
|
|
7865
8156
|
|
|
7866
8157
|
```typescript
|
|
7867
8158
|
job('patch-prod', {
|
|
@@ -7889,7 +8180,7 @@ A `when: 'drift'` gate on a step without a `check` facet, or at job/workflow sco
|
|
|
7889
8180
|
|
|
7890
8181
|
## Mandatory vs. explicit gates
|
|
7891
8182
|
|
|
7892
|
-
`approval` is the **explicit** gate — a deliberate "pause for a human here" written by the workflow author. It composes with the **mandatory** gate an operator can attach to a protected environment via required reviewers (see [Environments](environments
|
|
8183
|
+
`approval` is the **explicit** gate — a deliberate "pause for a human here" written by the workflow author. It composes with the **mandatory** gate an operator can attach to a protected environment via required reviewers (see [Environments](https://docs.kici.dev/user/environments/#required-reviewers)). When both apply to the same job, all clauses from both sources must be satisfied before the job is released. The two funnel into one held-element mechanism, so the dashboard queue and `kici approve` work the same way regardless of which source held the element.
|
|
7893
8184
|
|
|
7894
8185
|
## Approving from the CLI
|
|
7895
8186
|
|
|
@@ -7909,7 +8200,7 @@ kici approve <run-id> --job migrate-and-deploy --step apply-migration
|
|
|
7909
8200
|
kici reject <run-id> --job deploy-production --reason "Wrong release branch"
|
|
7910
8201
|
```
|
|
7911
8202
|
|
|
7912
|
-
You must be eligible for at least one unsatisfied clause — being a member of a named team or being a named user. The orchestrator verifies eligibility against the operator-defined teams, so naming a team in your workflow can never let an ineligible person release the gate. The command reports whether the element was released, how many clauses remain, or that it was rejected. See [`kici approve`](cli-reference
|
|
8203
|
+
You must be eligible for at least one unsatisfied clause — being a member of a named team or being a named user. The orchestrator verifies eligibility against the operator-defined teams, so naming a team in your workflow can never let an ineligible person release the gate. The command reports whether the element was released, how many clauses remain, or that it was rejected. See [`kici approve`](https://docs.kici.dev/user/cli-reference/#kici-approve) for the full command reference.
|
|
7913
8204
|
|
|
7914
8205
|
### Inline approval and `--approve-all` in `kici run remote`
|
|
7915
8206
|
|
|
@@ -7923,14 +8214,14 @@ kici run remote deploy-prod --approve-all
|
|
|
7923
8214
|
|
|
7924
8215
|
`--approve-all` is **run-scoped** — it only auto-approves holds belonging to the run this invocation dispatched; there is no fleet-wide or account-wide auto-approve. Eligibility is still enforced per hold: if you are not eligible for a gate, that gate still blocks. Each auto-approved gate prints its payload before resolving and is recorded distinctly in the audit trail (`held_run.auto_approve`).
|
|
7925
8216
|
|
|
7926
|
-
You can also approve from the dashboard approval queue. See [Dashboard](dashboard/environments-and-secrets
|
|
8217
|
+
You can also approve from the dashboard approval queue. See [Dashboard](https://docs.kici.dev/user/dashboard/environments-and-secrets/#approval-queue).
|
|
7927
8218
|
|
|
7928
8219
|
## See also
|
|
7929
8220
|
|
|
7930
|
-
- [Idempotent steps](idempotent-steps
|
|
7931
|
-
- [Environments](environments
|
|
7932
|
-
- [Approval gates (operator guide)](
|
|
7933
|
-
- [Approval gates (architecture)](
|
|
8221
|
+
- [Idempotent steps](https://docs.kici.dev/user/idempotent-steps/) — the check/apply step facet that drift gates build on.
|
|
8222
|
+
- [Environments](https://docs.kici.dev/user/environments/) — operator-required reviewers on protected environments.
|
|
8223
|
+
- [Approval gates (operator guide)](https://docs.kici.dev/operator/approvals/) — teams, the approval queue, expiry, and self-approval.
|
|
8224
|
+
- [Approval gates (architecture)](https://docs.kici.dev/architecture/approvals/) — the unified hold model and the step-level round-trip.
|
|
7934
8225
|
|
|
7935
8226
|
---
|
|
7936
8227
|
|
|
@@ -7990,7 +8281,7 @@ group: () => 'deploy';
|
|
|
7990
8281
|
group: (ctx) => `deploy-${ctx.event.targetBranch ?? 'default'}`;
|
|
7991
8282
|
```
|
|
7992
8283
|
|
|
7993
|
-
The workflow-level group function is always evaluated **agent-side** at runtime -- the lock file records only that a group function exists (`hasGroup: true`), not the function itself. The agent loads the workflow source, calls the group function with `{ branch, event }`, and reports the evaluated key back to the orchestrator before step execution begins. This differs from job-level `concurrencyGroup` (see [Environments](environments
|
|
8284
|
+
The workflow-level group function is always evaluated **agent-side** at runtime -- the lock file records only that a group function exists (`hasGroup: true`), not the function itself. The agent loads the workflow source, calls the group function with `{ branch, event }`, and reports the evaluated key back to the orchestrator before step execution begins. This differs from job-level `concurrencyGroup` (see [Environments](https://docs.kici.dev/user/environments/#concurrency-groups)), where the compiler performs purity analysis and can inline pure functions for orchestrator-side evaluation.
|
|
7994
8285
|
|
|
7995
8286
|
## cancelInProgress mode
|
|
7996
8287
|
|
|
@@ -8141,7 +8432,7 @@ Queued runs can be cancelled before they start executing. The cancel request rem
|
|
|
8141
8432
|
|
|
8142
8433
|
## Job-level concurrency groups
|
|
8143
8434
|
|
|
8144
|
-
In addition to workflow-level concurrency, individual jobs can define their own concurrency group via the `concurrencyGroup` property. This controls concurrent execution at the job level rather than the workflow level. See [Environments — concurrency groups](environments
|
|
8435
|
+
In addition to workflow-level concurrency, individual jobs can define their own concurrency group via the `concurrencyGroup` property. This controls concurrent execution at the job level rather than the workflow level. See [Environments — concurrency groups](https://docs.kici.dev/user/environments/#concurrency-groups) for details.
|
|
8145
8436
|
|
|
8146
8437
|
## Local execution
|
|
8147
8438
|
|
|
@@ -8149,7 +8440,7 @@ In addition to workflow-level concurrency, individual jobs can define their own
|
|
|
8149
8440
|
|
|
8150
8441
|
Coordination is local only. Running the same workflow on two different machines does not serialize across them — that requires the orchestrator. For full cross-host enforcement (queueing across agents, dashboard visibility, `max > 1`), use `kici run remote` against a deployed orchestrator.
|
|
8151
8442
|
|
|
8152
|
-
Lock files live under `$XDG_RUNTIME_DIR/kici-local-locks/` on Linux, falling back to `os.tmpdir()/kici-local-locks-<uid>/`. A workflow whose `group` callback throws aborts the run with a clear error rather than running unprotected. See [`kici run local` — Concurrency enforcement](cli-reference
|
|
8443
|
+
Lock files live under `$XDG_RUNTIME_DIR/kici-local-locks/` on Linux, falling back to `os.tmpdir()/kici-local-locks-<uid>/`. A workflow whose `group` callback throws aborts the run with a clear error rather than running unprotected. See [`kici run local` — Concurrency enforcement](https://docs.kici.dev/user/cli-reference/#concurrency-enforcement) for the `KICI_LOCAL_LOCK_KILL_GRACE_MS` override and the diagnostic output emitted while contending on a busy lock.
|
|
8153
8444
|
|
|
8154
8445
|
The `kici run local --concurrency <n>` flag is a separate concept — it caps **job-level** parallelism within a single run (how many jobs from one workflow run at once), not cross-run serialization.
|
|
8155
8446
|
|
|
@@ -8169,14 +8460,14 @@ This guide is split across the following pages:
|
|
|
8169
8460
|
|
|
8170
8461
|
| Page | Covers |
|
|
8171
8462
|
| ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
8172
|
-
| [Getting started](
|
|
8173
|
-
| [Navigation and layout](
|
|
8174
|
-
| [Runs and logs](
|
|
8175
|
-
| [Settings](
|
|
8176
|
-
| [Workflows, diagnostics, and orchestrators](
|
|
8177
|
-
| [Environments, secrets, and approvals](
|
|
8178
|
-
| [Activity and DLQ](
|
|
8179
|
-
| [Account](
|
|
8463
|
+
| [Getting started](https://docs.kici.dev/user/dashboard/getting-started/) | Onboarding checklist and your organizations list |
|
|
8464
|
+
| [Navigation and layout](https://docs.kici.dev/user/dashboard/navigation/) | Sidebar, mobile nav, theme, time display, shortcuts, error pages |
|
|
8465
|
+
| [Runs and logs](https://docs.kici.dev/user/dashboard/runs/) | Run list, run detail, the log viewer |
|
|
8466
|
+
| [Settings](https://docs.kici.dev/user/dashboard/settings/) | Members, roles, teams, keys, sources, billing, security, support access |
|
|
8467
|
+
| [Workflows, diagnostics, and orchestrators](https://docs.kici.dev/user/dashboard/workflows-and-diagnostics/) | Registered workflows, infra health, per-cluster views |
|
|
8468
|
+
| [Environments, secrets, and approvals](https://docs.kici.dev/user/dashboard/environments-and-secrets/) | Environments, secret scopes, approval queue |
|
|
8469
|
+
| [Activity and DLQ](https://docs.kici.dev/user/dashboard/activity-and-dlq/) | Forensic activity log and dead-letter queue |
|
|
8470
|
+
| [Account](https://docs.kici.dev/user/dashboard/account/) | Personal account settings |
|
|
8180
8471
|
|
|
8181
8472
|
---
|
|
8182
8473
|
|
|
@@ -8294,7 +8585,7 @@ env: async (event) => {
|
|
|
8294
8585
|
- **Write pure functions whenever possible** to avoid the init-job delay. Most environment and env computations only need the event payload data.
|
|
8295
8586
|
- **Check compiler warnings** -- the compiler tells you when a function is classified as impure and explains why.
|
|
8296
8587
|
- **Runtime errors in inline expressions cause immediate job failure.** There is no fallback to the init-job path. If your pure function throws at runtime (e.g., accessing a property on `undefined`), the job fails immediately.
|
|
8297
|
-
- **The event parameter is the normalized event envelope** — the same shape rules receive as `ctx.event`: `{ type, action, targetBranch, sourceBranch, changedFiles, payload, … }` (see the [event payload reference](
|
|
8588
|
+
- **The event parameter is the normalized event envelope** — the same shape rules receive as `ctx.event`: `{ type, action, targetBranch, sourceBranch, changedFiles, payload, … }` (see the [event payload reference](https://docs.kici.dev/user/sdk/event-payloads/) for the complete schema). Narrow on `event.type` (`'push'`, `'pull_request'`, `'tag'`, …) to branch per trigger kind. The raw provider webhook body is nested at `event.payload` (for GitHub pushes: `payload.ref`, `payload.after`, `payload.repository`, …).
|
|
8298
8589
|
|
|
8299
8590
|
---
|
|
8300
8591
|
|
|
@@ -8461,7 +8752,7 @@ job('deploy-review', {
|
|
|
8461
8752
|
});
|
|
8462
8753
|
```
|
|
8463
8754
|
|
|
8464
|
-
A pure function like the one above (see [Dynamic values](dynamic-values
|
|
8755
|
+
A pure function like the one above (see [Dynamic values](https://docs.kici.dev/user/dynamic-values/)) is evaluated inline at dispatch with no init-job overhead. Dynamic environments that match a glob pattern (e.g., `review/*`) inherit the pattern's configuration, variables, and protection rules.
|
|
8465
8756
|
|
|
8466
8757
|
### Job-level environment variables
|
|
8467
8758
|
|
|
@@ -8485,7 +8776,7 @@ job('deploy', {
|
|
|
8485
8776
|
|
|
8486
8777
|
### Concurrency groups
|
|
8487
8778
|
|
|
8488
|
-
Jobs can define their own concurrency groups to control concurrent execution within an environment. For workflow-level concurrency (which applies to all jobs in a workflow), see [Concurrency groups](concurrency
|
|
8779
|
+
Jobs can define their own concurrency groups to control concurrent execution within an environment. For workflow-level concurrency (which applies to all jobs in a workflow), see [Concurrency groups](https://docs.kici.dev/user/concurrency/).
|
|
8489
8780
|
|
|
8490
8781
|
Control concurrent deployments to the same environment:
|
|
8491
8782
|
|
|
@@ -8534,7 +8825,7 @@ When a job targets an environment, variables are merged in this order (last wins
|
|
|
8534
8825
|
6. **Job env** -- from the `env` property in the SDK
|
|
8535
8826
|
7. **`setEnv()` calls** -- runtime modifications within steps
|
|
8536
8827
|
|
|
8537
|
-
> **Note:** Secrets are NOT part of the environment variable merge. They are delivered to the step context via IPC and accessed through `ctx.secrets`, not through `process.env`. See the [step context](
|
|
8828
|
+
> **Note:** Secrets are NOT part of the environment variable merge. They are delivered to the step context via IPC and accessed through `ctx.secrets`, not through `process.env`. See the [step context](https://docs.kici.dev/user/environments/#step-context) section above.
|
|
8538
8829
|
|
|
8539
8830
|
## Protection rules
|
|
8540
8831
|
|
|
@@ -8558,9 +8849,9 @@ Require manual approval before a job can proceed:
|
|
|
8558
8849
|
Required reviewers: alice, bob
|
|
8559
8850
|
```
|
|
8560
8851
|
|
|
8561
|
-
When reviewers are required, the job enters a "held" state. Reviewers can approve or reject via the dashboard, the [`kici approve`](cli-reference
|
|
8852
|
+
When reviewers are required, the job enters a "held" state. Reviewers can approve or reject via the dashboard, the [`kici approve`](https://docs.kici.dev/user/cli-reference/#kici-approve) command, or the API. Held runs expire after a configurable timeout.
|
|
8562
8853
|
|
|
8563
|
-
This operator-set rule is the **mandatory** form of an approval gate. Workflow authors can also declare gates in code with `approval` at step, job, or workflow level — see [Approval gates](approvals
|
|
8854
|
+
This operator-set rule is the **mandatory** form of an approval gate. Workflow authors can also declare gates in code with `approval` at step, job, or workflow level — see [Approval gates](https://docs.kici.dev/user/approvals/). Both forms use the same held-element mechanism and the same queue.
|
|
8564
8855
|
|
|
8565
8856
|
### Wait timer
|
|
8566
8857
|
|
|
@@ -8595,7 +8886,7 @@ Trust tier is determined by the contributor's identity link and CI trust RBAC le
|
|
|
8595
8886
|
|
|
8596
8887
|
The trust tier also affects which lock file is used for PR-triggered runs: trusted contributors use the PR head lock file, while known and unknown contributors use the base branch lock file. This prevents untrusted workflow modifications from affecting execution.
|
|
8597
8888
|
|
|
8598
|
-
See the [CI security architecture docs](
|
|
8889
|
+
See the [CI security architecture docs](https://docs.kici.dev/architecture/security/ci-security/) for the full trust resolution flow.
|
|
8599
8890
|
|
|
8600
8891
|
### Security approval queue
|
|
8601
8892
|
|
|
@@ -8629,7 +8920,7 @@ Navigate to **Settings > Environments** in the dashboard. Click **New environmen
|
|
|
8629
8920
|
- **Fixed** -- applies to jobs that declare exactly this environment name, like `staging` or `production`
|
|
8630
8921
|
- **Glob** -- applies to any environment name a job declares that matches the pattern, e.g. `review/*` matches a job with `environment: 'review/PR-123'`
|
|
8631
8922
|
|
|
8632
|
-
The environments list shows each environment's type, whether test runs may use it (the `allowLocalExecution` flag -- see the [testing guide](
|
|
8923
|
+
The environments list shows each environment's type, whether test runs may use it (the `allowLocalExecution` flag -- see the [testing guide](https://docs.kici.dev/user/testing-guide/)), and whether it is enabled.
|
|
8633
8924
|
|
|
8634
8925
|
### Environment detail page
|
|
8635
8926
|
|
|
@@ -8788,7 +9079,7 @@ genericWebhook({
|
|
|
8788
9079
|
|
|
8789
9080
|
**Config options:** `source` (required), `events`, `match`, `not`, `auth`, `path`, `description`.
|
|
8790
9081
|
|
|
8791
|
-
The `source` field MUST match the `--name` that an operator passed to `kici-admin source add generic --name <name>` when the source was created — that string is the source's identifier in the orchestrator. Generic webhook sources must be created by an operator before events can be received; see [Operator guide: event routing](
|
|
9082
|
+
The `source` field MUST match the `--name` that an operator passed to `kici-admin source add generic --name <name>` when the source was created — that string is the source's identifier in the orchestrator. Generic webhook sources must be created by an operator before events can be received; see [Operator guide: event routing](https://docs.kici.dev/operator/event-routing/) for setup instructions.
|
|
8792
9083
|
|
|
8793
9084
|
### Schedule events
|
|
8794
9085
|
|
|
@@ -8898,7 +9189,7 @@ This lookup is fast because the registration index is held in memory and refresh
|
|
|
8898
9189
|
|
|
8899
9190
|
### Cross-source webhook delivery
|
|
8900
9191
|
|
|
8901
|
-
The catch-all `webhook()` trigger (see [SDK reference: webhook()](sdk/triggers
|
|
9192
|
+
The catch-all `webhook()` trigger (see [SDK reference: webhook()](https://docs.kici.dev/user/sdk/triggers/#webhook)) participates in this same registration lookup, but with one twist: it fires for matching events arriving via **any** inbound webhook source in the same org, not just the source the workflow's repo is bound to. The orchestrator maintains a `(customerId, eventName)` index over webhook trigger registrations and consults it on every inbound generic webhook.
|
|
8902
9193
|
|
|
8903
9194
|
The lookup is structurally org-isolated — a generic webhook delivered to org A can never reach a workflow registered against org B, because foreign-org rows live in a different bucket of the index. When a webhook fires across sources, the runtime clone token, repo URL, and check-status posting all come from the **registration's** source bundle, not the inbound source. The inbound source contributes only the event payload.
|
|
8904
9195
|
|
|
@@ -9035,15 +9326,15 @@ export default workflow('post-deploy', {
|
|
|
9035
9326
|
});
|
|
9036
9327
|
```
|
|
9037
9328
|
|
|
9038
|
-
Custom events are delivered immediately when emitted (mid-workflow, not queued until workflow completion). See the [SDK reference: emitting events](sdk/validation-events
|
|
9329
|
+
Custom events are delivered immediately when emitted (mid-workflow, not queued until workflow completion). See the [SDK reference: emitting events](https://docs.kici.dev/user/sdk/validation-events/#emitting-events) section for the full `ctx.emit()` API.
|
|
9039
9330
|
|
|
9040
9331
|
## See also
|
|
9041
9332
|
|
|
9042
|
-
- [SDK reference: event triggers](sdk/triggers
|
|
9043
|
-
- [SDK reference: emitting events](sdk/validation-events
|
|
9044
|
-
- [Workflow patterns: workflow chaining](patterns/integrations
|
|
9045
|
-
- [Operator guide: event routing](
|
|
9046
|
-
- [Architecture: event system](
|
|
9333
|
+
- [SDK reference: event triggers](https://docs.kici.dev/user/sdk/triggers/#event-triggers) -- complete API signatures for all trigger builders
|
|
9334
|
+
- [SDK reference: emitting events](https://docs.kici.dev/user/sdk/validation-events/#emitting-events) -- `ctx.emit()` and `defineEvent()` API
|
|
9335
|
+
- [Workflow patterns: workflow chaining](https://docs.kici.dev/user/patterns/integrations/#workflow-chaining) -- examples of event-driven workflow chains
|
|
9336
|
+
- [Operator guide: event routing](https://docs.kici.dev/operator/event-routing/) -- configuring generic webhook sources, trust relationships, and event routing
|
|
9337
|
+
- [Architecture: event system](https://docs.kici.dev/architecture/webhooks/event-system/) -- internal event routing design, registration model, cluster synchronization
|
|
9047
9338
|
|
|
9048
9339
|
---
|
|
9049
9340
|
|
|
@@ -9163,9 +9454,9 @@ Non-push triggers work too — `pr()`, `tag()`, `comment()`, `release()`, `workf
|
|
|
9163
9454
|
|
|
9164
9455
|
## See also
|
|
9165
9456
|
|
|
9166
|
-
- [Architecture — global workflows](
|
|
9167
|
-
- [Universal-git provider](providers/universal-git
|
|
9168
|
-
- [SDK reference](sdk-reference
|
|
9457
|
+
- [Architecture — global workflows](https://docs.kici.dev/architecture/global-workflows/) — dual-query dispatch flow, cross-provider auth, security model, lock-file schema.
|
|
9458
|
+
- [Universal-git provider](https://docs.kici.dev/user/providers/universal-git/#global-workflows) — how global workflows interact with `generic:<orgId>:<sourceId>` routing keys.
|
|
9459
|
+
- [SDK reference](https://docs.kici.dev/user/sdk-reference/) — the full set of triggers that accept `repos:`.
|
|
9169
9460
|
|
|
9170
9461
|
---
|
|
9171
9462
|
|
|
@@ -9292,9 +9583,9 @@ describing what would change. The rendering is read-only.
|
|
|
9292
9583
|
|
|
9293
9584
|
## See also
|
|
9294
9585
|
|
|
9295
|
-
- [Idempotent SDK helpers](
|
|
9296
|
-
- [Core SDK reference](
|
|
9297
|
-
- [Lock file and drift](
|
|
9586
|
+
- [Idempotent SDK helpers](https://docs.kici.dev/user/sdk/idempotent/) — the `idempotent()` / `idempotentStep()` convenience wrappers (always apply on drift), plus `checkStep()`, the clean-shape sibling that respects the run-level check mode.
|
|
9587
|
+
- [Core SDK reference](https://docs.kici.dev/user/sdk/core/) — the `step()`, `job()`, and `workflow()` factories the check facet extends.
|
|
9588
|
+
- [Lock file and drift](https://docs.kici.dev/user/lock-file-and-drift/) — how the lock file carries step capability flags.
|
|
9298
9589
|
|
|
9299
9590
|
---
|
|
9300
9591
|
|
|
@@ -9347,7 +9638,7 @@ export default workflow('build', {
|
|
|
9347
9638
|
|
|
9348
9639
|
Per-field rules:
|
|
9349
9640
|
|
|
9350
|
-
- **`url`** — Must be HTTPS. HTTP is permitted only for `localhost` / `127.0.0.0/8` / `::1` / `*.local` hosts, or when an operator has flipped the org-level `allow_http_npm_registries` toggle (see [`kici-admin org-settings allow-http-npm`](/operator/kici-admin-cli#allow-http-npm--permit-non-https-private-npm-registries)).
|
|
9641
|
+
- **`url`** — Must be HTTPS. HTTP is permitted only for `localhost` / `127.0.0.0/8` / `::1` / `*.local` hosts, or when an operator has flipped the org-level `allow_http_npm_registries` toggle (see [`kici-admin org-settings allow-http-npm`](https://docs.kici.dev/operator/kici-admin-cli#allow-http-npm--permit-non-https-private-npm-registries)).
|
|
9351
9642
|
- **`scope`** — Optional. When present, the registry serves only that scope (`@my-org`). When absent, this entry becomes the **default** registry — at most one entry may omit `scope`.
|
|
9352
9643
|
- **`tokenSecret`** — Mandatory `<environment>:<secret-name>`. The orchestrator looks up the secret in the named environment via the per-environment secret resolver. The bare name **must not** contain a colon.
|
|
9353
9644
|
- **`alwaysAuth`** — Defaults to `true`. Forces npm to send the token on every request (even GETs), which is what most managed-registry providers require.
|
|
@@ -9512,7 +9803,7 @@ registries: [
|
|
|
9512
9803
|
|
|
9513
9804
|
## Security model
|
|
9514
9805
|
|
|
9515
|
-
- **Per-environment scoping.** Every `tokenSecret` and `installEnv` entry is qualified with an environment name. The orchestrator runs the same protection-rule pipeline (branch / trust / concurrency / reviewer / wait-timer) against each named environment **before** resolving any secret, so a workflow that wants a `production` token from a feature branch is rejected exactly like a job that tries to deploy to `production` from a feature branch. A reviewer-gated install environment **pauses** the whole workflow dispatch as a workflow-scoped held run instead of resolving the token — see [Reviewer-gated installs](
|
|
9806
|
+
- **Per-environment scoping.** Every `tokenSecret` and `installEnv` entry is qualified with an environment name. The orchestrator runs the same protection-rule pipeline (branch / trust / concurrency / reviewer / wait-timer) against each named environment **before** resolving any secret, so a workflow that wants a `production` token from a feature branch is rejected exactly like a job that tries to deploy to `production` from a feature branch. A reviewer-gated install environment **pauses** the whole workflow dispatch as a workflow-scoped held run instead of resolving the token — see [Reviewer-gated installs](https://docs.kici.dev/user/private-registries/#reviewer-gated-installs) below.
|
|
9516
9807
|
- **Untrusted contributors get no tokens.** When a fork PR is dispatched and the contributor-trust resolution returns anything other than `trusted`, the orchestrator strips both `npmRegistries` and `installEnvSecrets` out of the dispatch. The install runs without auth and fails naturally on the first private dep — fork PRs cannot ever observe a registry token, even if a misconfigured environment lacks an explicit `requiredTrustTier`.
|
|
9517
9808
|
- **Lifecycle scripts disabled.** Whenever a private registry is in scope, the agent runs the install with `--ignore-scripts` (npm or pnpm alike). A malicious `preinstall` / `postinstall` hook in committed `package.json` cannot read the synthesized token env vars, even though they exist in the install subprocess. For a pnpm workspace, the agent builds your in-repo dependency closure as a separate step **after** the install's auth is torn down, so build scripts never see the tokens either.
|
|
9518
9809
|
- **Stderr is redacted.** If the install fails, the agent masks every token literal out of the surfaced stderr / stdout chunks before logging.
|
|
@@ -9548,9 +9839,9 @@ The dashboard JSON lives at `infra/terraform/modules/grafana/dashboards/install-
|
|
|
9548
9839
|
|
|
9549
9840
|
## See also
|
|
9550
9841
|
|
|
9551
|
-
- [Secrets](secrets
|
|
9552
|
-
- [Environments](environments
|
|
9553
|
-
- [Operator: `kici-admin org-settings`](/operator/kici-admin-cli#org-settings----org-level-security-policy) — the `allow_http_npm_registries` toggle and other org-scoped knobs.
|
|
9842
|
+
- [Secrets](https://docs.kici.dev/user/secrets/) — how to seed the `<environment>:<secret-name>` values referenced by `tokenSecret` / `installEnv`.
|
|
9843
|
+
- [Environments](https://docs.kici.dev/user/environments/) — protection rules (`branch_restrictions`, `requires_review`, `minimum_trust`) that the install gate inherits.
|
|
9844
|
+
- [Operator: `kici-admin org-settings`](https://docs.kici.dev/operator/kici-admin-cli#org-settings----org-level-security-policy) — the `allow_http_npm_registries` toggle and other org-scoped knobs.
|
|
9554
9845
|
|
|
9555
9846
|
---
|
|
9556
9847
|
|
|
@@ -9709,7 +10000,7 @@ kici verify-attestation ./dist/app.tgz \
|
|
|
9709
10000
|
--trust-root ./kici-trust-root.json
|
|
9710
10001
|
```
|
|
9711
10002
|
|
|
9712
|
-
The full flag reference is in the [CLI reference](
|
|
10003
|
+
The full flag reference is in the [CLI reference](https://docs.kici.dev/user/cli-reference/#kici-verify-attestation).
|
|
9713
10004
|
|
|
9714
10005
|
## Viewing attestations in the dashboard
|
|
9715
10006
|
|
|
@@ -9739,9 +10030,9 @@ attestations shows an empty state.
|
|
|
9739
10030
|
|
|
9740
10031
|
## See also
|
|
9741
10032
|
|
|
9742
|
-
- [SDK runtime reference](
|
|
10033
|
+
- [SDK runtime reference](https://docs.kici.dev/user/sdk/runtime/) — the `ctx.attestProvenance` and
|
|
9743
10034
|
`ctx.kici.oidc.token` step APIs in full.
|
|
9744
|
-
- [CLI reference](
|
|
10035
|
+
- [CLI reference](https://docs.kici.dev/user/cli-reference/#kici-verify-attestation) — every
|
|
9745
10036
|
`kici verify-attestation` flag and exit code.
|
|
9746
10037
|
|
|
9747
10038
|
---
|
|
@@ -9754,7 +10045,7 @@ KiCI provides an explicit secrets API that gives workflow steps controlled acces
|
|
|
9754
10045
|
|
|
9755
10046
|
## Overview
|
|
9756
10047
|
|
|
9757
|
-
Secrets are managed per-environment in the orchestrator (see [operator docs](/operator/orchestrator/configuration) for setup). When a job runs with an `environment` binding, the agent receives the secret keys available for that environment but does **not** inject their values into the step's process environment. Instead, steps access secrets through the `ctx.secrets` API.
|
|
10048
|
+
Secrets are managed per-environment in the orchestrator (see [operator docs](https://docs.kici.dev/operator/orchestrator/configuration) for setup). When a job runs with an `environment` binding, the agent receives the secret keys available for that environment but does **not** inject their values into the step's process environment. Instead, steps access secrets through the `ctx.secrets` API.
|
|
9758
10049
|
|
|
9759
10050
|
This design prevents accidental secret leakage through child processes, log output, or error messages. Only secrets you explicitly request are loaded into memory.
|
|
9760
10051
|
|
|
@@ -9773,7 +10064,7 @@ Use whichever fits the workflow — most small teams stay on the dashboard; ops
|
|
|
9773
10064
|
|
|
9774
10065
|
### When the operator has disabled dashboard writes
|
|
9775
10066
|
|
|
9776
|
-
The orchestrator operator can flip `secrets.set` (and `variables.set`) to **CLI-only** as part of the [dashboard-write policy](/operator/security/dashboard-write-policy). When that flip is on:
|
|
10067
|
+
The orchestrator operator can flip `secrets.set` (and `variables.set`) to **CLI-only** as part of the [dashboard-write policy](https://docs.kici.dev/operator/security/dashboard-write-policy). When that flip is on:
|
|
9777
10068
|
|
|
9778
10069
|
- The dashboard's "Add secret" / "Edit value" controls render with a lock icon. Clicking them shows a tooltip with the exact `kici-admin secret set` invocation needed.
|
|
9779
10070
|
- The dashboard's secrets page still lists secret **names**, scopes, and bindings — only the value-entry path moves to the CLI.
|
|
@@ -9810,7 +10101,7 @@ Two cross-cutting flags help every mode:
|
|
|
9810
10101
|
|
|
9811
10102
|
`kici-admin variable set` uses the same flags for non-encrypted variables, plus `--locked` to mark a variable as immutable from subsequent dashboard writes.
|
|
9812
10103
|
|
|
9813
|
-
A full reference of input modes — including the default-mode resolution rules and the security trade-offs of each — lives in [Dashboard-write policy → CLI input modes](/operator/security/dashboard-write-policy#cli-input-modes-for-the-plaintext-path).
|
|
10104
|
+
A full reference of input modes — including the default-mode resolution rules and the security trade-offs of each — lives in [Dashboard-write policy → CLI input modes](https://docs.kici.dev/operator/security/dashboard-write-policy#cli-input-modes-for-the-plaintext-path).
|
|
9814
10105
|
|
|
9815
10106
|
## Accessing secrets
|
|
9816
10107
|
|
|
@@ -9968,7 +10259,7 @@ export default workflow('deploy', {
|
|
|
9968
10259
|
|
|
9969
10260
|
### Injecting decrypted sops values into the environment
|
|
9970
10261
|
|
|
9971
|
-
KiCI does **not** scan your repository for `*.enc.yaml` files and auto-decrypt them into the environment at job init — nothing in a job runs `sops` on your behalf, and resolved secrets are never auto-injected as environment variables (see [Security notes](
|
|
10262
|
+
KiCI does **not** scan your repository for `*.enc.yaml` files and auto-decrypt them into the environment at job init — nothing in a job runs `sops` on your behalf, and resolved secrets are never auto-injected as environment variables (see [Security notes](https://docs.kici.dev/user/sdk/runtime/#security-notes)). Decryption is always something your workflow does explicitly: provision the age (or other) decryption key as a KiCI secret, expose it for the step, run `sops -d`, and decide what to do with the output.
|
|
9972
10263
|
|
|
9973
10264
|
When you want the decrypted values available as environment variables — not just written to a file — decrypt early and export the values through `$KICI_ENV` (or `ctx.setEnv`). Anything appended to `$KICI_ENV` becomes an environment variable for every later step in the same job, so a single decrypt step can populate the environment for the whole job:
|
|
9974
10265
|
|
|
@@ -10001,9 +10292,9 @@ export default workflow('deploy', {
|
|
|
10001
10292
|
});
|
|
10002
10293
|
```
|
|
10003
10294
|
|
|
10004
|
-
Decrypted values exported this way follow the same rules as any other `$KICI_ENV` / `ctx.setEnv` export: last-write-wins on a repeated key, and a key that collides with an operator-injected secret is ignored (the operator value wins). See [Exporting env from shell commands](
|
|
10295
|
+
Decrypted values exported this way follow the same rules as any other `$KICI_ENV` / `ctx.setEnv` export: last-write-wins on a repeated key, and a key that collides with an operator-injected secret is ignored (the operator value wins). See [Exporting env from shell commands](https://docs.kici.dev/user/sdk/runtime/#exporting-env-from-shell-commands-kici_env--kici_path) for the full `$KICI_ENV` contract.
|
|
10005
10296
|
|
|
10006
|
-
If you only need the decrypted material as a file on disk (the common `kubectl` / `gcloud` case), skip the env hop and redirect to a file instead — see the [canonical sops example](
|
|
10297
|
+
If you only need the decrypted material as a file on disk (the common `kubectl` / `gcloud` case), skip the env hop and redirect to a file instead — see the [canonical sops example](https://docs.kici.dev/user/secrets/#canonical-sops-example) above.
|
|
10007
10298
|
|
|
10008
10299
|
## API reference
|
|
10009
10300
|
|
|
@@ -10045,7 +10336,7 @@ Note that `get()` is async -- you must `await` the result.
|
|
|
10045
10336
|
|
|
10046
10337
|
When you run `kici types`, the compiler generates a `.kici/secrets.d.ts` file that provides type-safe autocompletion for your secret keys. The generated types augment the `StepSecrets` interface so that `ctx.secrets.get('...')` and `ctx.secrets.has('...')` offer suggestions for known keys.
|
|
10047
10338
|
|
|
10048
|
-
See [CLI reference](/user/cli) for the `kici types` command.
|
|
10339
|
+
See [CLI reference](https://docs.kici.dev/user/cli) for the `kici types` command.
|
|
10049
10340
|
|
|
10050
10341
|
---
|
|
10051
10342
|
|
|
@@ -10060,11 +10351,11 @@ The **GitHub App** is KiCI's flagship source. A single App:
|
|
|
10060
10351
|
1. receives `push`, `pull_request`, and related events from every repo it's installed on,
|
|
10061
10352
|
2. clones repos with a short-lived installation token (no deploy key to manage),
|
|
10062
10353
|
3. posts workflow / job / step Check runs back to the pull request (see
|
|
10063
|
-
[GitHub checks architecture](
|
|
10354
|
+
[GitHub checks architecture](https://docs.kici.dev/architecture/webhooks/github-checks/)).
|
|
10064
10355
|
|
|
10065
10356
|
You don't need an App for every scenario — if you only care about `push`
|
|
10066
10357
|
events, don't want to install an App, or are using a non-GitHub forge,
|
|
10067
|
-
use the [universal-git provider](universal-git
|
|
10358
|
+
use the [universal-git provider](https://docs.kici.dev/user/providers/universal-git/) instead.
|
|
10068
10359
|
|
|
10069
10360
|
## GitHub App vs. `github-repo` preset
|
|
10070
10361
|
|
|
@@ -10136,9 +10427,9 @@ GitHub App "my-org" is live.
|
|
|
10136
10427
|
|
|
10137
10428
|
| Flag | Effect |
|
|
10138
10429
|
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
10139
|
-
| `--name <name>` | The App name you _request_ on GitHub (required). GitHub assigns the final name + slug; the stored, displayed name always comes from GitHub (see [Display name and slug](
|
|
10430
|
+
| `--name <name>` | The App name you _request_ on GitHub (required). GitHub assigns the final name + slug; the stored, displayed name always comes from GitHub (see [Display name and slug](https://docs.kici.dev/user/providers/github/#display-name-and-slug)). |
|
|
10140
10431
|
| `--github-org <slug>` | Create the App under a GitHub organization instead of your personal account. |
|
|
10141
|
-
| `--webhook-url <url>` | **Advanced / self-hosted.** Bake this `https://` URL into the App's webhook verbatim and skip the platform-mode webhook-URL resolution (so it works even where the auto-resolved KiCI Platform URL is unavailable). See [Self-hosted webhook URL](
|
|
10432
|
+
| `--webhook-url <url>` | **Advanced / self-hosted.** Bake this `https://` URL into the App's webhook verbatim and skip the platform-mode webhook-URL resolution (so it works even where the auto-resolved KiCI Platform URL is unavailable). See [Self-hosted webhook URL](https://docs.kici.dev/user/providers/github/#self-hosted-webhook-url-override). |
|
|
10142
10433
|
| `--no-browser` | Headless mode: the CLI prints a `kici.dev` URL to open, then reads the setup code you paste back. The page is pure client-side — it only displays the short-lived code, which is useless once the CLI exchanges it. |
|
|
10143
10434
|
|
|
10144
10435
|
The manifest flow always creates a **new** App on GitHub. If a source for
|
|
@@ -10201,7 +10492,7 @@ the manifest flow — follow these steps.
|
|
|
10201
10492
|
|
|
10202
10493
|
The first four rows cover the core flow (clone, trigger matching,
|
|
10203
10494
|
Check runs). The **Organization -> Members** row is only relevant if
|
|
10204
|
-
you use [CI trust tiers](
|
|
10495
|
+
you use [CI trust tiers](https://docs.kici.dev/architecture/security/ci-security/)
|
|
10205
10496
|
on an org-level install — see the event note below.
|
|
10206
10497
|
|
|
10207
10498
|
6. **Subscribe to events.** At minimum: `push`, `pull_request`,
|
|
@@ -10213,7 +10504,7 @@ the manifest flow — follow these steps.
|
|
|
10213
10504
|
KiCI caches each pull-request author's repository access level (used
|
|
10214
10505
|
to decide whether workflow changes take effect immediately or are
|
|
10215
10506
|
held for approval — see
|
|
10216
|
-
[CI security](
|
|
10507
|
+
[CI security](https://docs.kici.dev/architecture/security/ci-security/)). These
|
|
10217
10508
|
events let the orchestrator drop stale cache entries the moment a
|
|
10218
10509
|
contributor's access changes. They are not required for correctness:
|
|
10219
10510
|
without them the cache simply ages out on its own 15-minute TTL, so a
|
|
@@ -10284,7 +10575,7 @@ kici-admin source get-webhook-secret github:12345 # Fetch the secret (for debug
|
|
|
10284
10575
|
```
|
|
10285
10576
|
|
|
10286
10577
|
For the full CLI reference see the `source` section of the
|
|
10287
|
-
[kici-admin CLI reference](
|
|
10578
|
+
[kici-admin CLI reference](https://docs.kici.dev/operator/orchestrator/kici-admin-cli/).
|
|
10288
10579
|
|
|
10289
10580
|
## Routing keys
|
|
10290
10581
|
|
|
@@ -10379,7 +10670,7 @@ kici-admin org-settings global-workflows deny-add 'my-org/contrib/*' \
|
|
|
10379
10670
|
Global workflows authored in a GitHub App repo can dispatch against
|
|
10380
10671
|
events from universal-git sources in the same org, and vice versa,
|
|
10381
10672
|
with each clone using its own source's credentials. See
|
|
10382
|
-
[Global workflows](
|
|
10673
|
+
[Global workflows](https://docs.kici.dev/architecture/global-workflows/) for the
|
|
10383
10674
|
policy model and cross-source dispatch contract.
|
|
10384
10675
|
|
|
10385
10676
|
## Check runs
|
|
@@ -10397,7 +10688,7 @@ configuration is required beyond installing the App with the
|
|
|
10397
10688
|
`checks: write` permission.
|
|
10398
10689
|
|
|
10399
10690
|
For architecture details see
|
|
10400
|
-
[GitHub checks architecture](
|
|
10691
|
+
[GitHub checks architecture](https://docs.kici.dev/architecture/webhooks/github-checks/).
|
|
10401
10692
|
|
|
10402
10693
|
## Rotation
|
|
10403
10694
|
|
|
@@ -10480,12 +10771,12 @@ and confirm the App is installed on that repo.
|
|
|
10480
10771
|
|
|
10481
10772
|
## See also
|
|
10482
10773
|
|
|
10483
|
-
- [Universal-git provider](universal-git
|
|
10774
|
+
- [Universal-git provider](https://docs.kici.dev/user/providers/universal-git/) — for Forgejo / Gitea /
|
|
10484
10775
|
Gogs / GitLab, and for plain-GitHub repos without an App
|
|
10485
|
-
- [GitHub checks architecture](
|
|
10486
|
-
- [Global workflows](
|
|
10487
|
-
- [kici-admin CLI reference](
|
|
10488
|
-
- [Event routing](
|
|
10776
|
+
- [GitHub checks architecture](https://docs.kici.dev/architecture/webhooks/github-checks/)
|
|
10777
|
+
- [Global workflows](https://docs.kici.dev/architecture/global-workflows/)
|
|
10778
|
+
- [kici-admin CLI reference](https://docs.kici.dev/operator/orchestrator/kici-admin-cli/)
|
|
10779
|
+
- [Event routing](https://docs.kici.dev/operator/event-routing/) — operator-level
|
|
10489
10780
|
routing-key mechanics
|
|
10490
10781
|
|
|
10491
10782
|
---
|
|
@@ -10504,8 +10795,8 @@ from GitHub or GitLab; the operator drives runs explicitly with the
|
|
|
10504
10795
|
> globally-registered or policy workflows the operator controls — a vendored
|
|
10505
10796
|
> repo baked into an agent image, a repo synced onto a host out-of-band, a
|
|
10506
10797
|
> golden internal pipeline. For ordinary per-developer CI driven by pull
|
|
10507
|
-
> requests and pushes, use the [GitHub App provider](
|
|
10508
|
-
> [universal-git provider](
|
|
10798
|
+
> requests and pushes, use the [GitHub App provider](https://docs.kici.dev/user/providers/github/) or the
|
|
10799
|
+
> [universal-git provider](https://docs.kici.dev/user/providers/universal-git/) against a real forge.
|
|
10509
10800
|
|
|
10510
10801
|
## Trust caveat (read first)
|
|
10511
10802
|
|
|
@@ -10600,9 +10891,9 @@ the run is created and then fails, rather than being silently dropped.
|
|
|
10600
10891
|
|
|
10601
10892
|
## See also
|
|
10602
10893
|
|
|
10603
|
-
- [Universal-git provider](
|
|
10894
|
+
- [Universal-git provider](https://docs.kici.dev/user/providers/universal-git/) — for a remote forge (or any
|
|
10604
10895
|
`http://` git server) when there is no shared filesystem.
|
|
10605
|
-
- [GitHub App provider](
|
|
10896
|
+
- [GitHub App provider](https://docs.kici.dev/user/providers/github/) — the flagship source for pull-request
|
|
10606
10897
|
CI with Checks.
|
|
10607
10898
|
|
|
10608
10899
|
---
|
|
@@ -10617,7 +10908,7 @@ Gitea, Gogs, GitLab, plain GitHub (without the App), and any custom
|
|
|
10617
10908
|
webhook-driven forge you can describe in JSONPath.
|
|
10618
10909
|
|
|
10619
10910
|
> **Want Check runs on pull requests?** Use the [GitHub App
|
|
10620
|
-
> provider](github
|
|
10911
|
+
> provider](https://docs.kici.dev/user/providers/github/) instead — it clones via short-lived installation
|
|
10621
10912
|
> tokens and drives KiCI's enriched Checks UI out of the box. The
|
|
10622
10913
|
> universal-git `github-repo` preset is the right fallback when you
|
|
10623
10914
|
> can't install an App.
|
|
@@ -10637,7 +10928,7 @@ that back the GitHub App source also serve universal-git sources.
|
|
|
10637
10928
|
> the right choice for the **remote-agent** case — point it at an `http://`
|
|
10638
10929
|
> git server and the agent clones over the network. When the repo instead
|
|
10639
10930
|
> lives on the agent's own filesystem (a vendored / operator-curated repo),
|
|
10640
|
-
> use a [local `file://` source](
|
|
10931
|
+
> use a [local `file://` source](https://docs.kici.dev/user/providers/local-file/) and drive it with the
|
|
10641
10932
|
> `kici-admin` CLI.
|
|
10642
10933
|
|
|
10643
10934
|
## Which preset do I need?
|
|
@@ -10811,7 +11102,7 @@ kici-admin org-settings global-workflows deny-add \
|
|
|
10811
11102
|
--source "generic:<orgId>:<sourceId>"
|
|
10812
11103
|
```
|
|
10813
11104
|
|
|
10814
|
-
See [Global workflows](
|
|
11105
|
+
See [Global workflows](https://docs.kici.dev/architecture/global-workflows/) for the
|
|
10815
11106
|
policy model (`isWorkflowRepoAllowed` + `isSourceRepoAllowed` +
|
|
10816
11107
|
`isElevatedAccessAllowed`) and the cross-provider dispatch contract.
|
|
10817
11108
|
|
|
@@ -10855,6 +11146,374 @@ with the right PEM.
|
|
|
10855
11146
|
|
|
10856
11147
|
# Architecture overview
|
|
10857
11148
|
|
|
11149
|
+
## Configuration architecture
|
|
11150
|
+
|
|
11151
|
+
Source: https://docs.kici.dev/architecture/configuration/
|
|
11152
|
+
|
|
11153
|
+
This document describes the internal design of the orchestrator's configuration management system. For operator-facing documentation, see [Configuration Reference](https://docs.kici.dev/operator/orchestrator/configuration/) and [Config Management Guide](https://docs.kici.dev/operator/orchestrator/config-management/).
|
|
11154
|
+
|
|
11155
|
+
## Config type system
|
|
11156
|
+
|
|
11157
|
+
The configuration is modeled as three distinct types that merge into a final application config:
|
|
11158
|
+
|
|
11159
|
+
### LocalConfig
|
|
11160
|
+
|
|
11161
|
+
Per-orchestrator settings loaded from a YAML file. These are instance-specific and never shared:
|
|
11162
|
+
|
|
11163
|
+
```typescript
|
|
11164
|
+
interface LocalConfig {
|
|
11165
|
+
database: { url: string };
|
|
11166
|
+
instance?: { id?: string; mode?: 'platform' | 'hybrid' | 'independent' };
|
|
11167
|
+
server?: { port?: number; basePath?: string; logLevel?: string };
|
|
11168
|
+
scaler?: { configPath?: string; configDir?: string };
|
|
11169
|
+
}
|
|
11170
|
+
```
|
|
11171
|
+
|
|
11172
|
+
**Key property:** Every field except `database.url` is optional. An orchestrator can run entirely from env vars with no YAML file.
|
|
11173
|
+
|
|
11174
|
+
### SharedConfig
|
|
11175
|
+
|
|
11176
|
+
Shared settings stored in the PostgreSQL `config_versions` table. Defined once, shared across all instances:
|
|
11177
|
+
|
|
11178
|
+
```typescript
|
|
11179
|
+
interface SharedConfig {
|
|
11180
|
+
platform?: { url?: string; token?: string };
|
|
11181
|
+
storage?: { type?: 's3'; bucket?: string; ... };
|
|
11182
|
+
agentAuth?: 'token' | 'none';
|
|
11183
|
+
agentTokenTtlMs?: number;
|
|
11184
|
+
queue?: { maxDepth?: number; timeoutMs?: number };
|
|
11185
|
+
lockfileCache?: { max?: number; ttlMs?: number };
|
|
11186
|
+
staleDetector?: { scanIntervalMs?: number; ... };
|
|
11187
|
+
secrets?: { key?: string; keyFile?: string; bootstrapAdminToken?: string };
|
|
11188
|
+
pgCustomerSecrets?: boolean;
|
|
11189
|
+
cluster?: { joinToken?: string; raftElectionTimeoutMinMs?: number; ... };
|
|
11190
|
+
// ... tuning fields
|
|
11191
|
+
}
|
|
11192
|
+
```
|
|
11193
|
+
|
|
11194
|
+
**Key property:** All top-level fields are optional. The DB may store a partial config.
|
|
11195
|
+
|
|
11196
|
+
### AppConfig
|
|
11197
|
+
|
|
11198
|
+
The merged result type used throughout the codebase. Combines `LocalConfig` + `SharedConfig` with resolved defaults:
|
|
11199
|
+
|
|
11200
|
+
```typescript
|
|
11201
|
+
interface AppConfig {
|
|
11202
|
+
instanceId: string; // From local config or auto-generated
|
|
11203
|
+
mode: 'platform' | 'hybrid' | 'independent';
|
|
11204
|
+
databaseUrl: string; // Flattened from database.url
|
|
11205
|
+
port: number; // Flattened from server.port
|
|
11206
|
+
basePath: string;
|
|
11207
|
+
platformUrl?: string; // Flattened from platform.url
|
|
11208
|
+
platformToken?: string;
|
|
11209
|
+
agentAuth: 'token' | 'none'; // With defaults applied
|
|
11210
|
+
queueMaxDepth: number; // Flattened from queue.maxDepth
|
|
11211
|
+
cluster: { instanceId: string; credentialFile: string; autoRotateCredentials: boolean; peers: string[]; ... };
|
|
11212
|
+
// ... all other fields with defaults
|
|
11213
|
+
}
|
|
11214
|
+
```
|
|
11215
|
+
|
|
11216
|
+
**Key property:** `AppConfig` uses flat field names (e.g., `databaseUrl` instead of `database.url`) for backward compatibility with the existing codebase. A `flattenToAppConfig()` function handles the mapping.
|
|
11217
|
+
|
|
11218
|
+
### How they merge
|
|
11219
|
+
|
|
11220
|
+
```
|
|
11221
|
+
defaults (getDefaults())
|
|
11222
|
+
|
|
|
11223
|
+
v
|
|
11224
|
+
SharedConfig (from DB) ──deepMerge──> merged layer 1+2
|
|
11225
|
+
|
|
|
11226
|
+
v
|
|
11227
|
+
LocalConfig (from YAML) ──deepMerge──> merged layer 1+2+3
|
|
11228
|
+
|
|
|
11229
|
+
v
|
|
11230
|
+
Env var overrides ──apply──> merged layer 1+2+3+4
|
|
11231
|
+
|
|
|
11232
|
+
v
|
|
11233
|
+
flattenToAppConfig() ──flatten──> flat AppConfig shape
|
|
11234
|
+
|
|
|
11235
|
+
v
|
|
11236
|
+
appConfigSchema.safeParse() ──validate──> typed AppConfig
|
|
11237
|
+
```
|
|
11238
|
+
|
|
11239
|
+
The `deepMerge` function merges objects recursively, replaces arrays (does not merge item-by-item), and skips `undefined`/`null` source values (they do not override existing values).
|
|
11240
|
+
|
|
11241
|
+
## Resolution chain
|
|
11242
|
+
|
|
11243
|
+
### Two-phase design
|
|
11244
|
+
|
|
11245
|
+
```
|
|
11246
|
+
Phase 1 (local-only):
|
|
11247
|
+
YAML file + KICI_ env vars
|
|
11248
|
+
|
|
|
11249
|
+
v
|
|
11250
|
+
resolveLocalConfig() -> { databaseUrl, instanceId, port, mode }
|
|
11251
|
+
|
|
|
11252
|
+
v
|
|
11253
|
+
Connect to PostgreSQL
|
|
11254
|
+
|
|
|
11255
|
+
v
|
|
11256
|
+
Phase 2 (full merge):
|
|
11257
|
+
defaults -> DB -> YAML -> env
|
|
11258
|
+
|
|
|
11259
|
+
v
|
|
11260
|
+
resolveFullConfig() -> AppConfig
|
|
11261
|
+
```
|
|
11262
|
+
|
|
11263
|
+
**Why two phases?** The database URL must come from local config (YAML or env var) because we need it to connect to PostgreSQL. But the shared config is stored in PostgreSQL. This circular dependency is broken by resolving local config first (Phase 1), connecting to the DB, then doing the full merge (Phase 2).
|
|
11264
|
+
|
|
11265
|
+
### Env var processing
|
|
11266
|
+
|
|
11267
|
+
Environment variables are processed in two stages:
|
|
11268
|
+
|
|
11269
|
+
1. **Direct mappings:** `KICI_DATABASE_URL` -> `database.url`, etc. A lookup table in `env-overlay.ts` maps known env var suffixes to config path arrays.
|
|
11270
|
+
|
|
11271
|
+
2. **Multi-app GitHub provider:** `KICI_PROVIDERS_GITHUB_<APP_NAME>_<FIELD>` is parsed by stripping the `PROVIDERS_GITHUB_` prefix, finding the field suffix (`APP_ID`, `PRIVATE_KEY`, `WEBHOOK_SECRET`), and deriving the app name from the middle segment. App names are lowercased with underscores converted to hyphens (`MAIN_ORG` -> `main-org`).
|
|
11272
|
+
|
|
11273
|
+
Type coercion is applied based on known field types: numeric fields are parsed as numbers, boolean fields are compared against `"true"`, all others remain strings.
|
|
11274
|
+
|
|
11275
|
+
## Two-phase bootstrap
|
|
11276
|
+
|
|
11277
|
+
```
|
|
11278
|
+
┌─────────────────┐
|
|
11279
|
+
│ Process Start │
|
|
11280
|
+
└────────┬────────┘
|
|
11281
|
+
│
|
|
11282
|
+
▼
|
|
11283
|
+
┌─────────────────┐
|
|
11284
|
+
│ Load YAML + │ resolveLocalConfig()
|
|
11285
|
+
│ Env Overrides │ -> databaseUrl, instanceId, port, mode
|
|
11286
|
+
└────────┬────────┘
|
|
11287
|
+
│
|
|
11288
|
+
▼
|
|
11289
|
+
┌─────────────────┐
|
|
11290
|
+
│ Connect to │ PostgreSQL
|
|
11291
|
+
│ Database │ Run migrations
|
|
11292
|
+
└────────┬────────┘
|
|
11293
|
+
│
|
|
11294
|
+
▼
|
|
11295
|
+
┌─────────────────┐
|
|
11296
|
+
│ Load Shared │ SharedConfigStore.getLatest()
|
|
11297
|
+
│ Config from DB │ -> decrypt -> SharedConfig
|
|
11298
|
+
└────────┬────────┘
|
|
11299
|
+
│
|
|
11300
|
+
▼
|
|
11301
|
+
┌─────────────────┐
|
|
11302
|
+
│ Full Merge │ resolveFullConfig(local, db, env)
|
|
11303
|
+
│ + Validate │ -> AppConfig
|
|
11304
|
+
└────────┬────────┘
|
|
11305
|
+
│
|
|
11306
|
+
▼
|
|
11307
|
+
┌─────────────────┐
|
|
11308
|
+
│ Start Server │ HTTP, WS, scaler, cluster
|
|
11309
|
+
└─────────────────┘
|
|
11310
|
+
```
|
|
11311
|
+
|
|
11312
|
+
## DB schema
|
|
11313
|
+
|
|
11314
|
+
### config_versions table
|
|
11315
|
+
|
|
11316
|
+
```sql
|
|
11317
|
+
CREATE TABLE config_versions (
|
|
11318
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
11319
|
+
version SERIAL NOT NULL UNIQUE,
|
|
11320
|
+
config JSONB NOT NULL,
|
|
11321
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
11322
|
+
created_by TEXT NOT NULL,
|
|
11323
|
+
description TEXT,
|
|
11324
|
+
encrypted_paths TEXT[] NOT NULL DEFAULT '{}'
|
|
11325
|
+
);
|
|
11326
|
+
|
|
11327
|
+
CREATE INDEX idx_config_versions_version ON config_versions(version DESC);
|
|
11328
|
+
CREATE INDEX idx_config_versions_created_at ON config_versions(created_at DESC);
|
|
11329
|
+
```
|
|
11330
|
+
|
|
11331
|
+
**Design choices:**
|
|
11332
|
+
|
|
11333
|
+
- **SERIAL version:** Auto-incrementing integer provides a total ordering of config changes. Simple to compare in heartbeats.
|
|
11334
|
+
- **JSONB config:** Stores the full shared config document. JSONB allows future querying/indexing if needed, though we always read the full document.
|
|
11335
|
+
- **Immutable rows:** Each change creates a new version. Old versions are never modified, providing a full audit trail.
|
|
11336
|
+
- **encrypted_paths:** Array of concrete dot-separated paths (e.g., `platform.token`, `cluster.joinToken`) that contain encrypted values. Stored alongside the config so the system knows exactly which fields to decrypt without runtime path list dependency.
|
|
11337
|
+
- **created_by:** Identifies the source of the change (e.g., `cli:seed`, `api:set`, `api:rollback`).
|
|
11338
|
+
|
|
11339
|
+
### Versioning strategy
|
|
11340
|
+
|
|
11341
|
+
- Version numbers are auto-incrementing integers managed by PostgreSQL SERIAL
|
|
11342
|
+
- Rollback creates a new version (copy of target) rather than reverting to the old version number
|
|
11343
|
+
- Example: versions 1, 2, 3 exist. Rollback to 1 creates version 4 with the content of version 1
|
|
11344
|
+
- This preserves the audit trail: version 4 records when and why the rollback happened
|
|
11345
|
+
|
|
11346
|
+
## Encryption
|
|
11347
|
+
|
|
11348
|
+
### Algorithm
|
|
11349
|
+
|
|
11350
|
+
AES-256-GCM via the existing `secrets/crypto.ts` module:
|
|
11351
|
+
|
|
11352
|
+
- **Key:** 32-byte AES-256 key derived from the master key (`KICI_SECRET_KEY`)
|
|
11353
|
+
- **IV:** Random 12-byte initialization vector per encryption
|
|
11354
|
+
- **Auth tag:** 16-byte GCM authentication tag
|
|
11355
|
+
- **AAD:** `config-field:<path>` (e.g., `config-field:platform.token`) -- binds ciphertext to its specific location
|
|
11356
|
+
- **Wire format:** base64(IV || AuthTag || Ciphertext)
|
|
11357
|
+
- **Key version:** Integer stamp for the master-key generation that sealed the row. Every new row is written under the active generation (hydrated from `MAX(key_version)` at startup). `kici-admin rotate-key` bumps the stamp atomically — the decrypt path accepts the current generation, and during the grace window also the previous one (`KICI_SECRET_KEY_OLD`), so historical rows and rollbacks continue to work seamlessly across rotations.
|
|
11358
|
+
|
|
11359
|
+
### Sensitive field paths
|
|
11360
|
+
|
|
11361
|
+
The following glob patterns define sensitive fields:
|
|
11362
|
+
|
|
11363
|
+
```typescript
|
|
11364
|
+
const SENSITIVE_FIELD_PATHS = [
|
|
11365
|
+
'platform.token',
|
|
11366
|
+
'secrets.key',
|
|
11367
|
+
'secrets.bootstrapAdminToken',
|
|
11368
|
+
'cluster.joinToken',
|
|
11369
|
+
] as const;
|
|
11370
|
+
```
|
|
11371
|
+
|
|
11372
|
+
### Encryption flow
|
|
11373
|
+
|
|
11374
|
+
```
|
|
11375
|
+
Save:
|
|
11376
|
+
config -> resolveGlobPaths(SENSITIVE_FIELD_PATHS)
|
|
11377
|
+
-> for each concrete path: encrypt(value, key, "config-field:<path>")
|
|
11378
|
+
-> store { encrypted_config, encrypted_paths[] }
|
|
11379
|
+
|
|
11380
|
+
Load:
|
|
11381
|
+
row -> for each path in encrypted_paths: decrypt(value, key, "config-field:<path>")
|
|
11382
|
+
-> SharedConfig
|
|
11383
|
+
|
|
11384
|
+
Export (redacted):
|
|
11385
|
+
row -> decrypt (if master key available) -> replace encrypted_paths values with "***REDACTED***"
|
|
11386
|
+
```
|
|
11387
|
+
|
|
11388
|
+
### Rollback optimization
|
|
11389
|
+
|
|
11390
|
+
When rolling back, the target version's encrypted config is copied as-is to the new version. No re-encryption is needed because:
|
|
11391
|
+
|
|
11392
|
+
- The same master key applies (all orchestrators share the same key)
|
|
11393
|
+
- The same AAD applies (paths are identical)
|
|
11394
|
+
- The `encrypted_paths` array is preserved from the target version
|
|
11395
|
+
|
|
11396
|
+
## Hot-Reload
|
|
11397
|
+
|
|
11398
|
+
### ConfigReloader design
|
|
11399
|
+
|
|
11400
|
+
The `ConfigReloader` class manages the full reload lifecycle:
|
|
11401
|
+
|
|
11402
|
+
```mermaid
|
|
11403
|
+
flowchart TD
|
|
11404
|
+
SIGHUP["SIGHUP (signal)"] --> trigger
|
|
11405
|
+
HTTP["HTTP POST /reload"] --> trigger
|
|
11406
|
+
Cluster["Cluster auto-fix"] --> trigger["triggerReload()<br/>500ms debounce"]
|
|
11407
|
+
|
|
11408
|
+
trigger --> execute["executeReload()<br/>boolean mutex"]
|
|
11409
|
+
|
|
11410
|
+
execute --> resolveLocal["resolveLocalConfig()"]
|
|
11411
|
+
execute --> getLatest["getLatest (DB)"]
|
|
11412
|
+
execute --> resolveFull["resolveFullConfig()<br/>(merge + validate)"]
|
|
11413
|
+
|
|
11414
|
+
resolveLocal --> check["Check restart-required fields"]
|
|
11415
|
+
getLatest --> check
|
|
11416
|
+
resolveFull --> check
|
|
11417
|
+
|
|
11418
|
+
check --> swap["Atomic swap<br/>onConfigApplied()"]
|
|
11419
|
+
|
|
11420
|
+
swap --> onProvider["onProviderChange (if changed)"]
|
|
11421
|
+
swap --> onScaler["onScalerReload (always)"]
|
|
11422
|
+
swap --> onPlatform["onPlatformReconnect (if changed)"]
|
|
11423
|
+
```
|
|
11424
|
+
|
|
11425
|
+
### Safety guarantees
|
|
11426
|
+
|
|
11427
|
+
- **Mutex:** Boolean flag prevents concurrent reloads. Second reload returns `{ success: false, errors: ["Reload already in progress"] }`.
|
|
11428
|
+
- **Debounce:** Rapid triggers (e.g., multiple SIGHUP signals) are collapsed into a single reload with a 500ms window.
|
|
11429
|
+
- **Validation before swap:** The new config must pass full schema validation. On failure, the old config is preserved and an error is logged.
|
|
11430
|
+
- **Restart-required detection:** Fields like `databaseUrl`, `port`, `instanceId` are compared. If changed, the old values are preserved in the applied config and a warning is logged.
|
|
11431
|
+
- **No crash on failure:** The orchestrator always keeps running with the old config if anything goes wrong during reload.
|
|
11432
|
+
|
|
11433
|
+
### Subsystem callbacks
|
|
11434
|
+
|
|
11435
|
+
The `ConfigReloader` uses a dependency injection pattern with callbacks for subsystem re-initialization:
|
|
11436
|
+
|
|
11437
|
+
| Callback | When Called | Purpose |
|
|
11438
|
+
| --------------------- | ----------------------------- | -------------------------------------------------------------------------------------------- |
|
|
11439
|
+
| `onProviderChange` | Provider config changed | Reserved callback (providers are now DB-managed via sources table; currently always a no-op) |
|
|
11440
|
+
| `onScalerReload` | Always on successful reload | Reload scaler YAML config |
|
|
11441
|
+
| `onPlatformReconnect` | Platform URL or token changed | Reconnect WS to Platform relay |
|
|
11442
|
+
| `onConfigApplied` | Always on successful reload | Atomic config reference swap, increment local config version |
|
|
11443
|
+
|
|
11444
|
+
### Prometheus metrics
|
|
11445
|
+
|
|
11446
|
+
| Metric | Type | Labels | Description |
|
|
11447
|
+
| ------------------------------- | ------- | ----------------------------------------------------------------------- | ------------------------------------- |
|
|
11448
|
+
| `kici_orch_config_reload_total` | Counter | `result` (attempted/success/failed), `source` (sighup/http/cluster/cli) | Config reload attempts and outcomes |
|
|
11449
|
+
| `kici_orch_config_version` | Gauge | -- | Current shared config version from DB |
|
|
11450
|
+
|
|
11451
|
+
## Multi-Provider
|
|
11452
|
+
|
|
11453
|
+
### ProviderRegistry
|
|
11454
|
+
|
|
11455
|
+
The `ProviderRegistry` maps routing keys to provider bundles. Each routing key (e.g., `github:12345`) is associated with a `ProviderBundle` containing:
|
|
11456
|
+
|
|
11457
|
+
- `WebhookNormalizer` -- normalizes incoming webhooks to a standard format
|
|
11458
|
+
- `LockFileFetcher` -- fetches lock files from the repository
|
|
11459
|
+
- `ChangedFilesFetcher` -- determines which files changed
|
|
11460
|
+
- `CloneTokenProvider` -- generates clone tokens for agents
|
|
11461
|
+
- `RepoUrlBuilder` -- builds clone URLs and raw file URLs
|
|
11462
|
+
- `ContributorResolver` -- resolves contributor permissions for trust-tier gating
|
|
11463
|
+
- `CheckStatusPoster` -- posts check statuses (approval/hold) to the git provider
|
|
11464
|
+
|
|
11465
|
+
Provider registrations are managed via the `sources` database table, not via `SharedConfig`. When the orchestrator connects to the Platform relay, it reads source records from the DB and sends `source.register` messages. Changes to sources (add/remove) are detected via PostgreSQL LISTEN/NOTIFY on the `sources_change` channel and pushed to the Platform via `source.secrets` and `source.register`/`source.deregister`.
|
|
11466
|
+
|
|
11467
|
+
### Per-App Credentials
|
|
11468
|
+
|
|
11469
|
+
Each source record contains its own `appId` and `privateKey` (stored as scoped secrets). When processing a webhook, the orchestrator looks up the routing key to find the matching source and uses its credentials for JWT generation, clone tokens, and check run updates.
|
|
11470
|
+
|
|
11471
|
+
## Cluster sync
|
|
11472
|
+
|
|
11473
|
+
### Heartbeat config version
|
|
11474
|
+
|
|
11475
|
+
In clustered deployments, each orchestrator includes its current config version in Raft heartbeat metadata via the `configVersion` optional field on the `peerHeartbeatSchema`.
|
|
11476
|
+
|
|
11477
|
+
When the `PeerRegistry` processes a heartbeat:
|
|
11478
|
+
|
|
11479
|
+
1. Compare `localConfigVersion` with `peer.configVersion`
|
|
11480
|
+
2. If `peer.configVersion > localConfigVersion` AND both are > 0:
|
|
11481
|
+
- Invoke the `onConfigVersionBehind` callback
|
|
11482
|
+
- This triggers a config reload from the database
|
|
11483
|
+
|
|
11484
|
+
### Auto-remediation flow
|
|
11485
|
+
|
|
11486
|
+
```
|
|
11487
|
+
Orchestrator A (version 5) Orchestrator B (version 3)
|
|
11488
|
+
│ │
|
|
11489
|
+
│──── heartbeat(configVersion=5) ────>│
|
|
11490
|
+
│ │
|
|
11491
|
+
│ compare: 5 > 3
|
|
11492
|
+
│ trigger reload from DB
|
|
11493
|
+
│ │
|
|
11494
|
+
│ resolveFullConfig()
|
|
11495
|
+
│ -> version 5
|
|
11496
|
+
│ │
|
|
11497
|
+
│<── heartbeat(configVersion=5) ──────│
|
|
11498
|
+
│ │
|
|
11499
|
+
│ both at version 5 ✓ │
|
|
11500
|
+
```
|
|
11501
|
+
|
|
11502
|
+
### Guard conditions
|
|
11503
|
+
|
|
11504
|
+
- Version comparison only triggers when **both** local and peer versions are > 0
|
|
11505
|
+
- This prevents false triggers from:
|
|
11506
|
+
- Legacy orchestrators that do not report `configVersion` (field is optional, defaults to 0)
|
|
11507
|
+
- Newly started orchestrators before their first config load
|
|
11508
|
+
- The `localConfigVersion` is a monotonically incrementing local counter (incremented on each successful reload)
|
|
11509
|
+
|
|
11510
|
+
## See also
|
|
11511
|
+
|
|
11512
|
+
- [Configuration Reference](https://docs.kici.dev/operator/orchestrator/configuration/) -- operator guide
|
|
11513
|
+
- [Config Management Guide](https://docs.kici.dev/operator/orchestrator/config-management/) -- CLI and API guide
|
|
11514
|
+
|
|
11515
|
+
---
|
|
11516
|
+
|
|
10858
11517
|
## Data flows
|
|
10859
11518
|
|
|
10860
11519
|
Source: https://docs.kici.dev/architecture/data-flows/
|
|
@@ -10892,7 +11551,7 @@ GitHub --> Platform Relay --> Orchestrator --> Agent
|
|
|
10892
11551
|
15. **Orchestrator matches triggers** against lock file using `matchAllWorkflows()` from `@kici-dev/engine`.
|
|
10893
11552
|
16. **Orchestrator checks caches** for source tarballs and dependency tarballs.
|
|
10894
11553
|
17. **Orchestrator dispatches jobs** to agents via the job queue and WebSocket.
|
|
10895
|
-
18. **Orchestrator persists a delivery row** keyed by `(org_id, delivery_id)` to its own `event_log`, including a pointer to the gzipped payload in object storage. The orchestrator's delivery log is surfaced in the dashboard's Settings → Event log tab. See [`webhook-delivery.md`](
|
|
11554
|
+
18. **Orchestrator persists a delivery row** keyed by `(org_id, delivery_id)` to its own `event_log`, including a pointer to the gzipped payload in object storage. The orchestrator's delivery log is surfaced in the dashboard's Settings → Event log tab. See [`webhook-delivery.md`](https://docs.kici.dev/architecture/webhooks/webhook-delivery/#delivery-log).
|
|
10896
11555
|
|
|
10897
11556
|
## Job execution flow
|
|
10898
11557
|
|
|
@@ -10920,7 +11579,7 @@ Orchestrator Agent Sandbox (child pro
|
|
|
10920
11579
|
|
|
10921
11580
|
### Agent pipeline
|
|
10922
11581
|
|
|
10923
|
-
The agent delegates job execution to an `ExecutionSandbox` (container, bare-metal, or firecracker). The sandbox runs customer code in an isolated child process -- never in the agent's V8 isolate. Four job types are handled: execution jobs (sandbox), build-only jobs (in-process, cache population), init-only jobs (in-process, dynamic field resolution), and DynamicJobFn evaluation jobs (in-process, runtime job generation). See [Job execution lifecycle](
|
|
11582
|
+
The agent delegates job execution to an `ExecutionSandbox` (container, bare-metal, or firecracker). The sandbox runs customer code in an isolated child process -- never in the agent's V8 isolate. Four job types are handled: execution jobs (sandbox), build-only jobs (in-process, cache population), init-only jobs (in-process, dynamic field resolution), and DynamicJobFn evaluation jobs (in-process, runtime job generation). See [Job execution lifecycle](https://docs.kici.dev/architecture/execution/job-execution/) for details.
|
|
10924
11583
|
|
|
10925
11584
|
1. **Report running** -- Send `job.status: running` immediately upon accepting the dispatch
|
|
10926
11585
|
2. **Sandbox selection** -- Determine execution mode (container, bare-metal, firecracker) from job config and environment
|
|
@@ -10958,7 +11617,7 @@ Run initiation (`upload-init`), the trigger, status polling, log retrieval, and
|
|
|
10958
11617
|
|
|
10959
11618
|
### Data plane
|
|
10960
11619
|
|
|
10961
|
-
The working-tree overlay tarball uploads **directly** from the developer machine to the orchestrator's object store via a pre-signed PUT URL minted during `upload-init`. The overlay bytes never pass through the Platform. Because of this split, only the object store needs to be reachable from the developer machine — the orchestrator can sit behind a private network. See [Storage layout](
|
|
11620
|
+
The working-tree overlay tarball uploads **directly** from the developer machine to the orchestrator's object store via a pre-signed PUT URL minted during `upload-init`. The overlay bytes never pass through the Platform. Because of this split, only the object store needs to be reachable from the developer machine — the orchestrator can sit behind a private network. See [Storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/) for the upload-endpoint configuration.
|
|
10962
11621
|
|
|
10963
11622
|
### Org anchor
|
|
10964
11623
|
|
|
@@ -11081,7 +11740,7 @@ If cache storage is unavailable or a download fails:
|
|
|
11081
11740
|
- **Source tarball download failure:** Hard failure today — the agent does not fall back to `git clone` on the execution path. (The build path is where cloning happens.) In practice this is rare because the same orchestrator that issued the pre-signed URL controls the cache backend.
|
|
11082
11741
|
- **Dep tarball download failure:** Agent falls back to running `npm ci` / `npm install` inline.
|
|
11083
11742
|
- **Dep tarball hash mismatch:** Agent retries the download twice (3 total attempts), then fails the job (no fallback for integrity failures).
|
|
11084
|
-
- **Source tarball drift (extracted `contentHash` ≠ lock file):** Hard failure with "Lock file is out of date: workflow source changed without regenerating kici.lock.json" — see [Lock file and drift](
|
|
11743
|
+
- **Source tarball drift (extracted `contentHash` ≠ lock file):** Hard failure with "Lock file is out of date: workflow source changed without regenerating kici.lock.json" — see [Lock file and drift](https://docs.kici.dev/user/lock-file-and-drift/).
|
|
11085
11744
|
- **Build failure:** Execution is skipped entirely with a "Build failed" check status. Workflows that contain dynamic job entries (DynamicJobFn) are allowed to proceed with their dynamic eval jobs since those compile from source.
|
|
11086
11745
|
- **No cache configured:** Agent runs inline install for every job (pre-caching behavior).
|
|
11087
11746
|
|
|
@@ -11113,9 +11772,9 @@ The orchestrator derives the target platform/arch for dep cache lookups by probi
|
|
|
11113
11772
|
|
|
11114
11773
|
### TTL and eviction (touch-on-read)
|
|
11115
11774
|
|
|
11116
|
-
Both caches refresh TTL on read via `touch-on-read`. An entry's lifetime is reset every time an orchestrator issues a pre-signed GET URL for it. Default TTL is `KICI_CACHE_TTL_DAYS=30`; entries unused for 30 days expire at the storage level. Actively used sources and deps stay in cache indefinitely as long as they continue to be referenced by inbound webhooks or reruns. See [`docs/operator/dependency-caching.md`](
|
|
11775
|
+
Both caches refresh TTL on read via `touch-on-read`. An entry's lifetime is reset every time an orchestrator issues a pre-signed GET URL for it. Default TTL is `KICI_CACHE_TTL_DAYS=30`; entries unused for 30 days expire at the storage level. Actively used sources and deps stay in cache indefinitely as long as they continue to be referenced by inbound webhooks or reruns. See [`docs/operator/dependency-caching.md`](https://docs.kici.dev/operator/dependency-caching/#cache-behavior) for configuration.
|
|
11117
11776
|
|
|
11118
|
-
For the full per-package bucket and prefix inventory — cache, logs, cold-store, and the observability sidecar buckets — see [orchestrator storage layout](
|
|
11777
|
+
For the full per-package bucket and prefix inventory — cache, logs, cold-store, and the observability sidecar buckets — see [orchestrator storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/).
|
|
11119
11778
|
|
|
11120
11779
|
### Pre-signed URL upload flow
|
|
11121
11780
|
|
|
@@ -11153,7 +11812,7 @@ Agents receive pre-signed S3 GET URLs (15-minute expiry) directly in `job.dispat
|
|
|
11153
11812
|
|
|
11154
11813
|
## User-facing cache flow
|
|
11155
11814
|
|
|
11156
|
-
The source/dep cache above is internal: the orchestrator owns its keys and decides when to hit or build. The **user-facing cache** is driven by the workflow author — the declarative `cache: { key, paths, restoreKeys? }` on a job/step, or the imperative `ctx.cache.restore()` / `ctx.cache.save()` API (see [SDK caching reference](
|
|
11815
|
+
The source/dep cache above is internal: the orchestrator owns its keys and decides when to hit or build. The **user-facing cache** is driven by the workflow author — the declarative `cache: { key, paths, restoreKeys? }` on a job/step, or the imperative `ctx.cache.restore()` / `ctx.cache.save()` API (see [SDK caching reference](https://docs.kici.dev/user/sdk/caching/)). It reuses the same object-storage backend and the same direct-to-storage presigned-URL transport, but the agent — not the orchestrator — initiates each restore and save over WebSocket.
|
|
11157
11816
|
|
|
11158
11817
|
The agent's cache module archives `paths` into a gzipped tarball (computing a SHA-256 over the bytes) and streams downloads back through a checksum-verified extract pipeline. The orchestrator's `UserCache` owns the `cache/<orgId>/<repoId>/<scope>/<key>` namespacing, the immutable first-save check, the `restoreKeys` prefix scan, the two-phase atomic save, and per-org quota/TTL eviction.
|
|
11159
11818
|
|
|
@@ -11207,7 +11866,7 @@ The save is **immutable** and **atomic**. The orchestrator declines (`skip: true
|
|
|
11207
11866
|
|
|
11208
11867
|
### Trust → scope mapping
|
|
11209
11868
|
|
|
11210
|
-
The orchestrator threads a `cacheRefScope` onto each `job.dispatch`. A **trusted** ref (the repo's own branches, default branch) maps to the `shared` write scope; any other ref (a fork PR) maps to `isolated`, writing to a per-run `iso/<runId>/` scope. This is the cache-isolation model: a fork can restore from the trusted `shared/` cache but can never write into it, so it cannot poison the entries a trusted branch later restores. The org segment of the key namespace (`cache/<orgId>/`) is the per-tenant boundary — no tenant can read another tenant's cache. See [orchestrator storage layout](
|
|
11869
|
+
The orchestrator threads a `cacheRefScope` onto each `job.dispatch`. A **trusted** ref (the repo's own branches, default branch) maps to the `shared` write scope; any other ref (a fork PR) maps to `isolated`, writing to a per-run `iso/<runId>/` scope. This is the cache-isolation model: a fork can restore from the trusted `shared/` cache but can never write into it, so it cannot poison the entries a trusted branch later restores. The org segment of the key namespace (`cache/<orgId>/`) is the per-tenant boundary — no tenant can read another tenant's cache. See [orchestrator storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/#user-cache) for the full prefix map and quota/TTL knobs.
|
|
11211
11870
|
|
|
11212
11871
|
## Internal event routing flow
|
|
11213
11872
|
|
|
@@ -11701,15 +12360,15 @@ The Platform tier exposes a `/ws/browser` WebSocket endpoint for dashboard clien
|
|
|
11701
12360
|
|
|
11702
12361
|
## See also
|
|
11703
12362
|
|
|
11704
|
-
- [Architecture overview](overview
|
|
11705
|
-
- [Protocol messages](protocol-messages
|
|
11706
|
-
- [Event system internals](
|
|
11707
|
-
- [State machine](
|
|
11708
|
-
- [Webhook delivery](
|
|
11709
|
-
- [Operator: dependency caching](
|
|
11710
|
-
- [Operator: monitoring & tracing](
|
|
11711
|
-
- [Operator: event routing & generic webhooks](
|
|
11712
|
-
- [SDK reference: output chaining](
|
|
12363
|
+
- [Architecture overview](https://docs.kici.dev/architecture/overview/) -- three-tier model and component responsibilities
|
|
12364
|
+
- [Protocol messages](https://docs.kici.dev/architecture/protocol-messages/) -- WebSocket message schemas
|
|
12365
|
+
- [Event system internals](https://docs.kici.dev/architecture/webhooks/event-system/) -- event router, registration model, cron scheduler
|
|
12366
|
+
- [State machine](https://docs.kici.dev/architecture/execution/state-machine/) -- job execution state transitions
|
|
12367
|
+
- [Webhook delivery](https://docs.kici.dev/architecture/webhooks/webhook-delivery/) -- detailed webhook processing pipeline
|
|
12368
|
+
- [Operator: dependency caching](https://docs.kici.dev/operator/dependency-caching/) -- configuration guide
|
|
12369
|
+
- [Operator: monitoring & tracing](https://docs.kici.dev/operator/observability/monitoring/) -- trace fields and Loki queries
|
|
12370
|
+
- [Operator: event routing & generic webhooks](https://docs.kici.dev/operator/event-routing/) -- generic source setup and trust management
|
|
12371
|
+
- [SDK reference: output chaining](https://docs.kici.dev/user/sdk/core/#output-chaining) -- user-facing output chaining API
|
|
11713
12372
|
|
|
11714
12373
|
---
|
|
11715
12374
|
|
|
@@ -11764,7 +12423,7 @@ The orchestrator is the execution brain. It decides what to run and dispatches w
|
|
|
11764
12423
|
- **Agent registry** -- Tracks connected agents with label-based routing for job dispatch.
|
|
11765
12424
|
- **Job queue** -- PostgreSQL-backed FIFO queue for reliable dispatch.
|
|
11766
12425
|
- **Webhook pipeline** -- Dedup, event mapping, lock file fetch, trigger matching, and job dispatch in a single pipeline.
|
|
11767
|
-
- **Multi-orchestrator clustering** -- Optional peer-to-peer coordination via direct WebSocket connections. Enables cross-architecture job routing (e.g., x64 coordinator reroutes arm64 jobs to a peer), high availability, and dedicated coordinator topologies. Uses Raft consensus for leader election (orphan recovery). See [Multi-Orchestrator Architecture](
|
|
12426
|
+
- **Multi-orchestrator clustering** -- Optional peer-to-peer coordination via direct WebSocket connections. Enables cross-architecture job routing (e.g., x64 coordinator reroutes arm64 jobs to a peer), high availability, and dedicated coordinator topologies. Uses Raft consensus for leader election (orphan recovery). See [Multi-Orchestrator Architecture](https://docs.kici.dev/architecture/clustering/multi-orchestrator/).
|
|
11768
12427
|
- **Auto-scaler** -- Optional pluggable module for ephemeral agent provisioning. Supports containers (Docker/Podman), bare-metal processes, and Firecracker microVMs as backends. Spawns agents on demand when no matching agent is connected, with label-based routing, two-level capacity limits (global + per-backend), warm pools, YAML configuration (`scalers.d/` directory support), and SIGHUP reload. Disabled by default -- orchestrator works without it.
|
|
11769
12428
|
- **Independent database** -- Has its own PostgreSQL database separate from the Platform. Stores execution runs/jobs/steps, dispatch queue, webhook secrets, dedup cache, and scaler state. The orchestrator's `execution_runs` and `execution_jobs` are the authoritative source of truth. The Platform receives execution status updates via WebSocket messages (`execution.status`, `job.status.forward`).
|
|
11770
12429
|
|
|
@@ -11903,13 +12562,13 @@ The orchestrator connects outbound to the Platform WebSocket endpoint. After aut
|
|
|
11903
12562
|
|
|
11904
12563
|
When multiple orchestrators are deployed, they establish direct WebSocket connections to each other on the `/ws/peer` endpoint. Peers are discovered via the Platform matchmaker (Platform/hybrid modes) or static configuration (`KICI_CLUSTER_PEERS` env var, independent mode). Connections are authenticated with a mutual pre-shared key (PSK). Traffic includes agent inventory heartbeats, job rerouting, progress reporting, cancel propagation, and Raft leader election. These messages never transit the Platform tier.
|
|
11905
12564
|
|
|
11906
|
-
> See [Multi-Orchestrator Architecture](
|
|
12565
|
+
> See [Multi-Orchestrator Architecture](https://docs.kici.dev/architecture/clustering/multi-orchestrator/) for clustering details and [Protocol Messages](https://docs.kici.dev/architecture/protocol/dashboard/#orchestrator-orchestrator-messages-peer-to-peer) for message schemas.
|
|
11907
12566
|
|
|
11908
12567
|
### Orchestrator ↔ Agent
|
|
11909
12568
|
|
|
11910
12569
|
The agent connects outbound to the orchestrator WebSocket endpoint. After registration (agent ID, labels, concurrency), the connection is used for job dispatch, status reporting, and log streaming.
|
|
11911
12570
|
|
|
11912
|
-
> See [Protocol Messages](protocol-messages
|
|
12571
|
+
> See [Protocol Messages](https://docs.kici.dev/architecture/protocol-messages/) and [Webhook Delivery](https://docs.kici.dev/architecture/webhooks/webhook-delivery/) for detailed message flows and schemas.
|
|
11913
12572
|
|
|
11914
12573
|
## Authentication and multi-tenancy
|
|
11915
12574
|
|
|
@@ -11917,9 +12576,9 @@ KiCI uses application-level tenant isolation. The Platform dashboard API accepts
|
|
|
11917
12576
|
|
|
11918
12577
|
## See also
|
|
11919
12578
|
|
|
11920
|
-
- [Multi-Orchestrator Architecture](
|
|
11921
|
-
- [State Machine](
|
|
11922
|
-
- [Protocol Messages](protocol-messages
|
|
11923
|
-
- [Webhook Delivery](
|
|
12579
|
+
- [Multi-Orchestrator Architecture](https://docs.kici.dev/architecture/clustering/multi-orchestrator/) -- P2P clustering, Raft consensus, job rerouting
|
|
12580
|
+
- [State Machine](https://docs.kici.dev/architecture/execution/state-machine/) -- execution lifecycle tracking across all tiers
|
|
12581
|
+
- [Protocol Messages](https://docs.kici.dev/architecture/protocol-messages/) -- WebSocket message schemas for all three layers
|
|
12582
|
+
- [Webhook Delivery](https://docs.kici.dev/architecture/webhooks/webhook-delivery/) -- end-to-end trace of a webhook through all three tiers
|
|
11924
12583
|
|
|
11925
12584
|
---
|