@kici-dev/compiler 0.1.21 → 0.1.22
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 +8 -4
- package/dist/commands/held-run-client.d.ts +7 -2
- package/dist/commands/held-run-client.js +9 -3
- package/dist/commands/held-run-resolve.d.ts +5 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +15 -7
- package/dist/commands/run-hold-watch.d.ts +57 -0
- package/dist/commands/run-hold-watch.js +87 -0
- package/dist/commands/run.d.ts +8 -0
- package/dist/commands/run.js +65 -4
- package/dist/commands/test.d.ts +10 -0
- package/dist/llm-context/llms-full.txt +344 -48
- package/dist/llm-context/llms.txt +2 -1
- package/dist/local-executor/job-runner.js +2 -1
- package/dist/lockfile/generator.js +34 -17
- package/dist/remote/config.d.ts +2 -0
- package/dist/remote/config.js +1 -0
- package/dist/remote/platform-client.d.ts +6 -1
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/step-context.js +2 -1
- package/dist/types.d.ts +11 -6
- package/package.json +4 -4
- package/sbom.spdx.json +35 -35
|
@@ -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
|
|
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`)](../sdk/core.md#job-dependencies-needs) in the SDK reference for the full matrix of `needs` forms (string, `Job` ref, `{ name, when }`, `dynamicGroup()`) and [needs-scheduler](../../architecture/execution/needs-scheduler.md) 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
|
|
|
@@ -1109,6 +1109,116 @@ This workflow:
|
|
|
1109
1109
|
|
|
1110
1110
|
---
|
|
1111
1111
|
|
|
1112
|
+
## Host restart & wait-for-alive
|
|
1113
|
+
|
|
1114
|
+
Source: https://docs.kici.dev/user/patterns/host-restart/
|
|
1115
|
+
|
|
1116
|
+
When a KiCI agent runs on a host you are provisioning, a workflow can reboot
|
|
1117
|
+
that host and resume work once it comes back — the Ansible `reboot` +
|
|
1118
|
+
`wait_for_connection` pattern, expressed as two jobs pinned to the same host.
|
|
1119
|
+
|
|
1120
|
+
## The two-job pattern
|
|
1121
|
+
|
|
1122
|
+
Host restart is a **job-boundary** capability: the reboot is the last step of a
|
|
1123
|
+
"restart" job, and the post-restart work lives in a **separate job** pinned to
|
|
1124
|
+
the same host that `needs` the restart job. The orchestrator holds the
|
|
1125
|
+
post-restart job until the host completes a reboot cycle, then dispatches it.
|
|
1126
|
+
|
|
1127
|
+
```typescript
|
|
1128
|
+
import { workflow, job, step, restartHost, waitForHostAlive } from '@kici-dev/sdk';
|
|
1129
|
+
|
|
1130
|
+
export default workflow('patch-and-verify', {
|
|
1131
|
+
on: [
|
|
1132
|
+
/* ... */
|
|
1133
|
+
],
|
|
1134
|
+
jobs: [
|
|
1135
|
+
// Restart job: apply updates, then reboot. restartHost() MUST be the last step.
|
|
1136
|
+
job('patch', {
|
|
1137
|
+
runsOn: 'kici:host:box-01',
|
|
1138
|
+
steps: [
|
|
1139
|
+
step('upgrade', async (ctx) => {
|
|
1140
|
+
await ctx.$`apt-get upgrade -y`;
|
|
1141
|
+
}),
|
|
1142
|
+
restartHost(),
|
|
1143
|
+
],
|
|
1144
|
+
}),
|
|
1145
|
+
// Post-restart job: pinned to the SAME host, needs the restart job.
|
|
1146
|
+
job('verify', {
|
|
1147
|
+
runsOn: 'kici:host:box-01',
|
|
1148
|
+
needs: ['patch'],
|
|
1149
|
+
steps: [
|
|
1150
|
+
waitForHostAlive(() => fetch('http://localhost:8080/health')),
|
|
1151
|
+
step('check-service', async (ctx) => {
|
|
1152
|
+
await ctx.$`systemctl is-active myservice`;
|
|
1153
|
+
}),
|
|
1154
|
+
],
|
|
1155
|
+
}),
|
|
1156
|
+
],
|
|
1157
|
+
});
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
## `restartHost()`
|
|
1161
|
+
|
|
1162
|
+
`restartHost(opts?)` reboots the host the job runs on. It signals the
|
|
1163
|
+
orchestrator that a reboot is pending (which holds the post-restart job and
|
|
1164
|
+
treats the agent's imminent disconnect as expected, not a failure), reports the
|
|
1165
|
+
step success, and the agent issues the OS reboot once the step completes.
|
|
1166
|
+
|
|
1167
|
+
- **Must be the last step** of its job — the job completes before the box goes
|
|
1168
|
+
down.
|
|
1169
|
+
- `deadlineMs` (optional) overrides how long the orchestrator waits for the host
|
|
1170
|
+
to return after the reboot. The default is the orchestrator's
|
|
1171
|
+
`KICI_HOST_REBOOT_DEADLINE_MS` (15 minutes). If the host does not reconnect by
|
|
1172
|
+
the deadline, the held post-restart job fails with a clear "host did not
|
|
1173
|
+
return after reboot" reason.
|
|
1174
|
+
- The reboot command is chosen per operating system (Linux `systemctl reboot`,
|
|
1175
|
+
macOS `shutdown -r now`, Windows `shutdown /r /t 0`).
|
|
1176
|
+
|
|
1177
|
+
## `waitForHostAlive(probe, opts?)`
|
|
1178
|
+
|
|
1179
|
+
`waitForHostAlive()` is the optional first step of the post-restart job. The
|
|
1180
|
+
baseline "the host is back" guarantee comes for free — the post-restart job only
|
|
1181
|
+
dispatches after the agent reconnects. `waitForHostAlive(probe)` adds a
|
|
1182
|
+
**service-readiness** gate on top: it polls `probe` until it resolves, for hosts
|
|
1183
|
+
where "agent connected" does not yet mean "services ready".
|
|
1184
|
+
|
|
1185
|
+
- The probe can return anything (an HTTP response, an open port check, a marker
|
|
1186
|
+
file). Any non-null resolution means "ready"; a throw or rejection keeps
|
|
1187
|
+
polling.
|
|
1188
|
+
- `intervalMs` (default 3000) and `timeoutMs` (default 300000) tune the poll. If
|
|
1189
|
+
the probe never succeeds within `timeoutMs`, the step fails with "services did
|
|
1190
|
+
not come up".
|
|
1191
|
+
|
|
1192
|
+
## Same-host pinning
|
|
1193
|
+
|
|
1194
|
+
The "same host" relationship is the pin: both jobs target the same host via
|
|
1195
|
+
`runsOn` (a `kici:host:<id>` label or another label the host carries), and the
|
|
1196
|
+
post-restart job `needs` the restart job. Durable provisioning hosts MUST set a
|
|
1197
|
+
stable agent id (`KICI_AGENT_ID`) so the host re-registers under the same
|
|
1198
|
+
identity after the reboot — that stable identity is what lets the orchestrator
|
|
1199
|
+
recognise the down-then-up cycle and release the held job.
|
|
1200
|
+
|
|
1201
|
+
## Failure behavior
|
|
1202
|
+
|
|
1203
|
+
- **Host never returns by the deadline** → the held post-restart job fails.
|
|
1204
|
+
- **Reboot privilege denied** → the restart step fails with a clear privilege
|
|
1205
|
+
error (see the operator note below), and the orchestrator clears the
|
|
1206
|
+
reboot-pending hold.
|
|
1207
|
+
- **`waitForHostAlive` probe never succeeds** → that step fails.
|
|
1208
|
+
|
|
1209
|
+
The orchestrator refuses to reboot the host it runs on, so a co-located agent
|
|
1210
|
+
cannot take down the orchestrator's own box.
|
|
1211
|
+
|
|
1212
|
+
## Operator prerequisite: reboot privilege
|
|
1213
|
+
|
|
1214
|
+
Rebooting needs host privilege. An agent used for host provisioning must be able
|
|
1215
|
+
to run the OS reboot primitive — run the agent service with reboot privilege, or
|
|
1216
|
+
grant a narrow `shutdown` / `systemctl reboot` permission. Agents used for
|
|
1217
|
+
provisioning generally need broad, near-root host privileges; see the operator
|
|
1218
|
+
agent documentation for the full posture.
|
|
1219
|
+
|
|
1220
|
+
---
|
|
1221
|
+
|
|
1112
1222
|
## Integration patterns
|
|
1113
1223
|
|
|
1114
1224
|
Source: https://docs.kici.dev/user/patterns/integrations/
|
|
@@ -2489,9 +2599,9 @@ const test = job('test', { needs: [lint], ... });
|
|
|
2489
2599
|
// 2. Reference by string name
|
|
2490
2600
|
const test = job('test', { needs: ['lint'], ... });
|
|
2491
2601
|
|
|
2492
|
-
// 3. Object form with per-edge
|
|
2602
|
+
// 3. Object form with a per-edge run condition (`when`)
|
|
2493
2603
|
const cleanup = job('cleanup', {
|
|
2494
|
-
needs: [{ name: 'build',
|
|
2604
|
+
needs: [{ name: 'build', when: 'always' }],
|
|
2495
2605
|
...
|
|
2496
2606
|
});
|
|
2497
2607
|
|
|
@@ -2502,16 +2612,36 @@ const deploy = job('deploy', {
|
|
|
2502
2612
|
});
|
|
2503
2613
|
```
|
|
2504
2614
|
|
|
2505
|
-
**
|
|
2615
|
+
**Run condition (`when`):** controls when a downstream edge is satisfied, based on the upstream's terminal status. `when` is keyword sugar (or a raw status-set) that resolves at compile time to the set of upstream terminal statuses that satisfy the edge. The downstream edge is satisfied when the upstream's terminal status is a member of that set.
|
|
2506
2616
|
|
|
2507
|
-
|
|
|
2508
|
-
|
|
|
2509
|
-
| `
|
|
2510
|
-
| `
|
|
2617
|
+
| Keyword | Satisfied when the upstream is… | Use for |
|
|
2618
|
+
| ------------------------ | ------------------------------- | ------------------------------------------- |
|
|
2619
|
+
| `'on-success'` (default) | `success` | normal dependencies |
|
|
2620
|
+
| `'always'` | any terminal status | cleanup / notification / teardown jobs |
|
|
2621
|
+
| `'on-skip'` | `success` or `skipped` | continue when an upstream was narrowed out |
|
|
2622
|
+
| `'on-failure'` | `failed` or `timed_out_stale` | error-handler jobs that run only on failure |
|
|
2511
2623
|
|
|
2512
|
-
|
|
2624
|
+
For full control, pass a raw status-set instead of a keyword: `when: ['skipped', 'failed', 'timed_out_stale']`. The valid members are the terminal job statuses: `success`, `failed`, `cancelled`, `skipped`, `timed_out_stale`, `drift_dropped`.
|
|
2513
2625
|
|
|
2514
|
-
|
|
2626
|
+
String and `Job`-reference entries default to `when: 'on-success'`. To override, use the object form (`{ name, when }` for static upstreams, `{ group, when }` for dynamic groups -- `dynamicGroup(name, { when: 'always' })` produces the latter).
|
|
2627
|
+
|
|
2628
|
+
When an upstream's terminal status is **not** in the edge's set, the downstream transitions directly to `skipped`. Because a skipped job is itself terminal, this propagates transitively: each downstream's `when` set governs whether the skip cascades further.
|
|
2629
|
+
|
|
2630
|
+
**Dispatch gate:** `needs` is a hard dispatch gate. A job dispatches only after every upstream in its `needs` array reaches a terminal status that satisfies that edge's `when` set. Root jobs (empty `needs`, no dynamic group refs) dispatch immediately. The scheduler is DB-backed and fully recovers across orchestrator restarts.
|
|
2631
|
+
|
|
2632
|
+
**Reading an upstream's status in a step:** inside a running job, `ctx.needs.<job>.status` exposes each upstream's terminal status (`success`, `failed`, `skipped`, …) and `ctx.needs.<job>.result` its outputs. A group / matrix / `runsOnAll` fan-out upstream is an ordered array of `{ name, result, status }`, one per child. Use this to branch in TypeScript:
|
|
2633
|
+
|
|
2634
|
+
```typescript
|
|
2635
|
+
job('report', {
|
|
2636
|
+
needs: [{ name: 'probe', when: 'always' }],
|
|
2637
|
+
run: async (ctx) => {
|
|
2638
|
+
if (ctx.needs.probe.status === 'failed') await fileIncident(ctx.needs.probe.result);
|
|
2639
|
+
else await publish(ctx.needs.probe.result);
|
|
2640
|
+
},
|
|
2641
|
+
});
|
|
2642
|
+
```
|
|
2643
|
+
|
|
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](../../architecture/execution/dynamic-jobs.md).
|
|
2515
2645
|
|
|
2516
2646
|
**DAG validation:** three-layer cycle detection.
|
|
2517
2647
|
|
|
@@ -2524,7 +2654,10 @@ String and `Job`-reference entries default to `ifFailed: 'skip'`. To override, u
|
|
|
2524
2654
|
Create a reference to a dynamic job group, for use inside a static job's `needs` array.
|
|
2525
2655
|
|
|
2526
2656
|
```typescript
|
|
2527
|
-
function dynamicGroup(
|
|
2657
|
+
function dynamicGroup(
|
|
2658
|
+
name: string,
|
|
2659
|
+
options?: { when?: 'on-success' | 'always' | 'on-skip' | 'on-failure' | string[] },
|
|
2660
|
+
): DynamicGroupRef;
|
|
2528
2661
|
```
|
|
2529
2662
|
|
|
2530
2663
|
Use when a static downstream must wait for every generated job tagged with a given group name to complete. If the dynamic group produces zero jobs, the downstream dispatches immediately (empty group satisfies all upstreams).
|
|
@@ -3445,10 +3578,10 @@ export default workflow('discovery-fan-out', { jobs: [discover, reports] });
|
|
|
3445
3578
|
|
|
3446
3579
|
`ctx.needs` shape:
|
|
3447
3580
|
|
|
3448
|
-
| Need form
|
|
3449
|
-
|
|
|
3450
|
-
| `'jobName'` / `{ name,
|
|
3451
|
-
| `dynamicGroup('g')` / `dynamicGroup('g', {
|
|
3581
|
+
| Need form | `ctx.needs[...]` value |
|
|
3582
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3583
|
+
| `'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
|
+
| `dynamicGroup('g')` / `dynamicGroup('g', { when })` | ordered array of `{ name, result, status }`, one per group member |
|
|
3452
3585
|
|
|
3453
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()`](./triggers.md) for cross-workflow reactions to a job finishing. See the architecture deep-dive in [dynamic jobs](../../architecture/execution/dynamic-jobs.md#result-aware-generation).
|
|
3454
3587
|
|
|
@@ -3685,6 +3818,32 @@ A held host occupies a wave slot indefinitely while it waits to reconnect, stall
|
|
|
3685
3818
|
roll behind an absent box. `skip` (run only reachable hosts) or `fail` (refuse the roll
|
|
3686
3819
|
if any expected host is down) keep the window moving.
|
|
3687
3820
|
|
|
3821
|
+
### Narrowing the roster at run time with `--target`
|
|
3822
|
+
|
|
3823
|
+
A `runsOnAll` predicate is authored once in the workflow, but you can narrow it for a
|
|
3824
|
+
single run with `kici run --target <selector>` — an Ansible-`--limit`-style runtime
|
|
3825
|
+
filter. The effective host set becomes `runsOnAll ∩ target`: the selector can only
|
|
3826
|
+
_remove_ hosts from the matched roster, never add them. The narrowing is **run-global**
|
|
3827
|
+
(it applies to every `runsOnAll` job) and **`runsOnAll`-only** (single `runsOn`-pinned
|
|
3828
|
+
jobs are untouched). Repeated `--target` values AND-combine — a host must satisfy every
|
|
3829
|
+
selector to survive.
|
|
3830
|
+
|
|
3831
|
+
```bash
|
|
3832
|
+
# Patch only the role:web subset of whatever role:* hosts the job would match
|
|
3833
|
+
kici run remote deploy --target role:web
|
|
3834
|
+
|
|
3835
|
+
# Intersect two selectors: hosts must be BOTH role:web AND dc:eu
|
|
3836
|
+
kici run remote deploy --target role:web --target dc:eu
|
|
3837
|
+
```
|
|
3838
|
+
|
|
3839
|
+
When `--target` narrows a `runsOnAll` job to zero hosts, the run **fails** by default
|
|
3840
|
+
(a mistyped selector should be loud, not silently no-op). Pass `--target-allow-empty`
|
|
3841
|
+
to **skip** the zeroed job instead — it records a `skipped` status, and downstream jobs
|
|
3842
|
+
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](./core.md#job-dependencies-needs)
|
|
3845
|
+
for how a skipped upstream propagates.
|
|
3846
|
+
|
|
3688
3847
|
### Limits (v0)
|
|
3689
3848
|
|
|
3690
3849
|
- Per-host secret scoping is not yet available — all hosts receive the job's resolved
|
|
@@ -5649,6 +5808,8 @@ kici run remote [fixture] [options]
|
|
|
5649
5808
|
| `--history` | `false` | Show table of recent test runs |
|
|
5650
5809
|
| `--context <ctx.key=value>` | none | Inject a namespaced context secret, uploaded encrypted (repeatable) |
|
|
5651
5810
|
| `--env <KEY=VALUE>` | none | Provide a per-run secret, uploaded encrypted (repeatable) — see [testing guide](testing-guide.md) |
|
|
5811
|
+
| `--target <selector>` | none | Narrow `runsOnAll` jobs to hosts matching this label selector (repeatable, AND-combined) |
|
|
5812
|
+
| `--target-allow-empty` | `false` | A `--target` that narrows a `runsOnAll` job to zero hosts skips it instead of failing |
|
|
5652
5813
|
| `--debug` | `false` | Verbose internals |
|
|
5653
5814
|
| `--kici-dir <path>` | `.kici` | Path to .kici directory |
|
|
5654
5815
|
|
|
@@ -5687,8 +5848,44 @@ kici run remote push-main --no-wait
|
|
|
5687
5848
|
|
|
5688
5849
|
# View recent test run history
|
|
5689
5850
|
kici run remote --history
|
|
5851
|
+
|
|
5852
|
+
# Narrow runsOnAll jobs to a subset of the host roster
|
|
5853
|
+
kici run remote deploy --target role:web
|
|
5854
|
+
|
|
5855
|
+
# AND-combine repeated --target values (hosts must match every selector)
|
|
5856
|
+
kici run remote deploy --target role:web --target dc:eu
|
|
5857
|
+
|
|
5858
|
+
# Skip a runsOnAll job instead of failing it when the target matches no host
|
|
5859
|
+
kici run remote deploy --target role:gpu --target-allow-empty
|
|
5690
5860
|
```
|
|
5691
5861
|
|
|
5862
|
+
#### Host narrowing with `--target`
|
|
5863
|
+
|
|
5864
|
+
`--target <selector>` is a runtime narrowing for `runsOnAll` jobs, analogous to
|
|
5865
|
+
Ansible's `--limit`. A `runsOnAll` job normally fans out to **every** roster host
|
|
5866
|
+
matching its predicate, one pinned execution per host. `--target` intersects that
|
|
5867
|
+
matched roster with a label selector, so the effective host set is
|
|
5868
|
+
`runsOnAll ∩ target`:
|
|
5869
|
+
|
|
5870
|
+
- **Narrow-only.** `--target` can only _remove_ hosts from the matched set, never
|
|
5871
|
+
add them. The widening dimension (OR across host groups) lives in the workflow's
|
|
5872
|
+
`runsOnAll`; `--target` only subtracts.
|
|
5873
|
+
- **Run-global, `runsOnAll`-only.** A single `--target` applies to every
|
|
5874
|
+
`runsOnAll` job in the run. Jobs pinned to a single host with `runsOn` are
|
|
5875
|
+
untouched.
|
|
5876
|
+
- **Repeatable and AND-combined.** Each `--target` value is its own selector; a
|
|
5877
|
+
host must satisfy **all** of them to survive the narrowing. Use a single value
|
|
5878
|
+
for an OR-style match within one selector and repeated values for AND.
|
|
5879
|
+
- **Selector syntax** matches `runsOn`: an exact label (`role:web`), a glob
|
|
5880
|
+
(`role:*`), or a regex (`/^box-0[1-3]$/`).
|
|
5881
|
+
|
|
5882
|
+
When `--target` narrows a `runsOnAll` job to zero hosts, the default is to **fail**
|
|
5883
|
+
the run (fail-loud — a typo in the selector shouldn't silently skip work). Pass
|
|
5884
|
+
`--target-allow-empty` to **skip** the zeroed job instead; the job records a
|
|
5885
|
+
`skipped` status, and any downstream job that needs it with `when: 'on-skip'` (or
|
|
5886
|
+
`when: 'always'`) still runs. See [Job dependencies](./sdk/core.md#job-dependencies-needs)
|
|
5887
|
+
for the `when` gating model.
|
|
5888
|
+
|
|
5692
5889
|
**Exit codes:**
|
|
5693
5890
|
|
|
5694
5891
|
| Code | Meaning |
|
|
@@ -5829,18 +6026,21 @@ By default, `kici login` opens your browser for OIDC authentication using PKCE.
|
|
|
5829
6026
|
|
|
5830
6027
|
After OAuth, the CLI exchanges the OIDC token for a personal access token (PAT) stored in the config directory (`~/.kici/config` by default, overridable with `KICI_CONFIG_DIR`).
|
|
5831
6028
|
|
|
6029
|
+
`kici login` targets the hosted KiCI Platform by default. To authenticate against another environment (a self-hosted Platform, for example), pass `--platform-endpoint` / `--oidc-issuer` or set `KICI_PLATFORM_URL` / `KICI_OIDC_ISSUER`. Login persists the platform endpoint and OIDC issuer it authenticated against alongside the PAT, so a saved PAT always matches its endpoint. Because the config describes one environment at a time, **switching the endpoint resets the active organization and default clusters** — re-run `kici org use <name>` after switching environments.
|
|
6030
|
+
|
|
5832
6031
|
```bash
|
|
5833
6032
|
kici login [options]
|
|
5834
6033
|
```
|
|
5835
6034
|
|
|
5836
6035
|
**Options:**
|
|
5837
6036
|
|
|
5838
|
-
| Option | Default | Description
|
|
5839
|
-
| --------------------------- | ------- |
|
|
5840
|
-
| `--token <key>` | none | API key for direct authentication (legacy)
|
|
5841
|
-
| `--device` | false | Force device authorization flow (headless/SSH)
|
|
5842
|
-
| `--platform-endpoint <url>` | none | Platform relay URL
|
|
5843
|
-
| `--
|
|
6037
|
+
| Option | Default | Description |
|
|
6038
|
+
| --------------------------- | ------- | --------------------------------------------------- |
|
|
6039
|
+
| `--token <key>` | none | API key for direct authentication (legacy) |
|
|
6040
|
+
| `--device` | false | Force device authorization flow (headless/SSH) |
|
|
6041
|
+
| `--platform-endpoint <url>` | none | Platform relay URL |
|
|
6042
|
+
| `--oidc-issuer <url>` | none | OIDC issuer URL (selects a non-default environment) |
|
|
6043
|
+
| `--routing-key <key>` | none | Routing key for webhook source identification |
|
|
5844
6044
|
|
|
5845
6045
|
**Environment variables:**
|
|
5846
6046
|
|
|
@@ -5866,7 +6066,8 @@ kici login --device
|
|
|
5866
6066
|
kici login --token kici_sk_abc123...
|
|
5867
6067
|
|
|
5868
6068
|
# Log in against a self-hosted Platform
|
|
5869
|
-
kici login --platform-endpoint https://platform.example.com
|
|
6069
|
+
kici login --platform-endpoint https://platform.example.com \
|
|
6070
|
+
--oidc-issuer https://auth.example.com/realms/kici-internal
|
|
5870
6071
|
|
|
5871
6072
|
# Suppress browser opening (print authorize URL to stdout)
|
|
5872
6073
|
KICI_BROWSER_CMD=none kici login
|
|
@@ -7520,12 +7721,14 @@ Source: https://docs.kici.dev/user/approvals/
|
|
|
7520
7721
|
|
|
7521
7722
|
An **approval gate** pauses execution until an authorized person approves it. Execution resumes from exactly where it paused; a rejection (or an expired hold) fails the run.
|
|
7522
7723
|
|
|
7523
|
-
You declare a gate in your workflow with `
|
|
7724
|
+
You declare a gate in your workflow with `approval`. It is available at three levels of granularity:
|
|
7524
7725
|
|
|
7525
7726
|
- **Step** — pause mid-job, before a specific step runs. The agent holds the live workspace (with all prior-step state intact) for the duration of the wait.
|
|
7526
7727
|
- **Job** — hold the job before any of its steps run.
|
|
7527
7728
|
- **Workflow** — hold the whole run before any job is dispatched.
|
|
7528
7729
|
|
|
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](#drift-gates-whendrift) below.
|
|
7731
|
+
|
|
7529
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)](../operator/approvals.md) for how operators define teams, the approval queue, and expiry; see [the architecture overview](../architecture/approvals.md) for how a hold is evaluated and resumed.
|
|
7530
7733
|
|
|
7531
7734
|
## Quick start
|
|
@@ -7540,7 +7743,7 @@ export default workflow('deploy', {
|
|
|
7540
7743
|
jobs: [
|
|
7541
7744
|
job('deploy-production', {
|
|
7542
7745
|
runsOn: 'default',
|
|
7543
|
-
|
|
7746
|
+
approval: [{ team: 'leads' }],
|
|
7544
7747
|
steps: [step('deploy', async (ctx) => ctx.$`deploy --prod`)],
|
|
7545
7748
|
}),
|
|
7546
7749
|
],
|
|
@@ -7549,28 +7752,28 @@ export default workflow('deploy', {
|
|
|
7549
7752
|
|
|
7550
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`](#approving-from-the-cli) command. Once a member of `leads` approves, the job dispatches normally.
|
|
7551
7754
|
|
|
7552
|
-
## The `
|
|
7755
|
+
## The `approval` field
|
|
7553
7756
|
|
|
7554
|
-
`
|
|
7757
|
+
`approval` accepts three forms.
|
|
7555
7758
|
|
|
7556
7759
|
### Shorthand: `true`
|
|
7557
7760
|
|
|
7558
7761
|
```typescript
|
|
7559
7762
|
job('deploy', {
|
|
7560
7763
|
runsOn: 'default',
|
|
7561
|
-
|
|
7764
|
+
approval: true,
|
|
7562
7765
|
steps: [
|
|
7563
7766
|
/* ... */
|
|
7564
7767
|
],
|
|
7565
7768
|
});
|
|
7566
7769
|
```
|
|
7567
7770
|
|
|
7568
|
-
`
|
|
7771
|
+
`approval: true` holds the element until **any** org member who can act on approvals signs off — anyone with the `environments:write` or `ci_trust:write` permission. Use it when you want a manual gate without restricting who may release it.
|
|
7569
7772
|
|
|
7570
7773
|
### Approver list (AND)
|
|
7571
7774
|
|
|
7572
7775
|
```typescript
|
|
7573
|
-
|
|
7776
|
+
approval: [{ team: 'leads' }, { user: 'cto' }],
|
|
7574
7777
|
```
|
|
7575
7778
|
|
|
7576
7779
|
A list of approver clauses is an **AND** list: every clause must be satisfied before the element is released.
|
|
@@ -7582,27 +7785,29 @@ A single approver may satisfy more than one clause. If `cto` is also a member of
|
|
|
7582
7785
|
|
|
7583
7786
|
There is no OR or nested logic — clauses are always a flat AND list.
|
|
7584
7787
|
|
|
7585
|
-
### Object form: reason and timeout
|
|
7788
|
+
### Object form: when, reason, and timeout
|
|
7586
7789
|
|
|
7587
7790
|
```typescript
|
|
7588
|
-
|
|
7791
|
+
approval: {
|
|
7792
|
+
when: 'always',
|
|
7589
7793
|
approvers: [{ team: 'security' }, { team: 'leads' }],
|
|
7590
7794
|
reason: 'Production deploy requires security + leads sign-off',
|
|
7591
7795
|
timeout: 7200, // seconds
|
|
7592
7796
|
},
|
|
7593
7797
|
```
|
|
7594
7798
|
|
|
7595
|
-
| Field | Type
|
|
7596
|
-
| ----------- |
|
|
7597
|
-
| `
|
|
7598
|
-
| `
|
|
7599
|
-
| `
|
|
7799
|
+
| Field | Type | Description |
|
|
7800
|
+
| ----------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
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](#drift-gates-whendrift). |
|
|
7802
|
+
| `approvers` | `ApproverClause[]` | The AND list of `{ team }` / `{ user }` clauses. An empty list means "any approval-capable member". |
|
|
7803
|
+
| `reason` | `string` | A human-readable label shown in the dashboard queue and the held-for-approval status check. |
|
|
7804
|
+
| `timeout` | `number` | Per-gate expiry in **seconds**, overriding the org default. On expiry the element is rejected. |
|
|
7600
7805
|
|
|
7601
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](../operator/approvals.md#expiry).
|
|
7602
7807
|
|
|
7603
7808
|
## Granularity
|
|
7604
7809
|
|
|
7605
|
-
The same `
|
|
7810
|
+
The same `approval` field is accepted on a workflow, a job, and a step.
|
|
7606
7811
|
|
|
7607
7812
|
### Workflow-level
|
|
7608
7813
|
|
|
@@ -7611,7 +7816,7 @@ A workflow-level gate holds the entire run before any job is dispatched:
|
|
|
7611
7816
|
```typescript
|
|
7612
7817
|
export default workflow('release', {
|
|
7613
7818
|
on: [push({ branches: ['main'] })],
|
|
7614
|
-
|
|
7819
|
+
approval: [{ team: 'release-managers' }],
|
|
7615
7820
|
jobs: [buildJob, publishJob],
|
|
7616
7821
|
});
|
|
7617
7822
|
```
|
|
@@ -7623,7 +7828,7 @@ A job-level gate holds just that job; other jobs in the run proceed normally:
|
|
|
7623
7828
|
```typescript
|
|
7624
7829
|
job('publish', {
|
|
7625
7830
|
runsOn: 'default',
|
|
7626
|
-
|
|
7831
|
+
approval: [{ team: 'leads' }],
|
|
7627
7832
|
steps: [
|
|
7628
7833
|
/* ... */
|
|
7629
7834
|
],
|
|
@@ -7640,7 +7845,7 @@ job('migrate-and-deploy', {
|
|
|
7640
7845
|
steps: [
|
|
7641
7846
|
step('build-plan', async (ctx) => ctx.$`./gen-migration-plan.sh`),
|
|
7642
7847
|
step('apply-migration', {
|
|
7643
|
-
|
|
7848
|
+
approval: [{ team: 'dba' }],
|
|
7644
7849
|
run: async (ctx) => ctx.$`./apply-migration.sh`,
|
|
7645
7850
|
}),
|
|
7646
7851
|
step('deploy', async (ctx) => ctx.$`deploy --prod`),
|
|
@@ -7652,9 +7857,39 @@ Here `build-plan` runs, then the job pauses for a `dba` approval. On approval, `
|
|
|
7652
7857
|
|
|
7653
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](../operator/approvals.md#agent-occupancy-during-step-level-holds).
|
|
7654
7859
|
|
|
7860
|
+
## Drift gates (`when: 'drift'`)
|
|
7861
|
+
|
|
7862
|
+
A `when: 'drift'` gate is **step-scope only** and requires a [check/apply step](idempotent-steps.md). 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
|
+
|
|
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](#approving-from-the-cli) show the actual diff the operator is approving — not a static reason string.
|
|
7865
|
+
|
|
7866
|
+
```typescript
|
|
7867
|
+
job('patch-prod', {
|
|
7868
|
+
runsOn: 'default',
|
|
7869
|
+
steps: [
|
|
7870
|
+
step('apply-nginx-config', {
|
|
7871
|
+
check: async (ctx) => ((await inSync(ctx)) ? null : { want: DESIRED_CONF }),
|
|
7872
|
+
summarize: (drift) => `would rewrite nginx.conf (${drift.want.length} bytes)`,
|
|
7873
|
+
run: async (ctx, drift) => {
|
|
7874
|
+
await writeConfig(drift.want);
|
|
7875
|
+
},
|
|
7876
|
+
approval: { when: 'drift', approvers: [{ team: 'ops' }], reason: 'prod patch' },
|
|
7877
|
+
}),
|
|
7878
|
+
],
|
|
7879
|
+
});
|
|
7880
|
+
```
|
|
7881
|
+
|
|
7882
|
+
Behavior:
|
|
7883
|
+
|
|
7884
|
+
- The gate fires **only in apply mode**. In `--check` mode nothing applies, so nothing gates — drift is just reported.
|
|
7885
|
+
- **Approve** → the step's `run(ctx, drift)` applies the change.
|
|
7886
|
+
- **Reject** → fail-stop: the step fails, the job fails, and the `needs:` skip-cascade aborts everything downstream.
|
|
7887
|
+
|
|
7888
|
+
A `when: 'drift'` gate on a step without a `check` facet, or at job/workflow scope, is a compile error.
|
|
7889
|
+
|
|
7655
7890
|
## Mandatory vs. explicit gates
|
|
7656
7891
|
|
|
7657
|
-
`
|
|
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.md#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.
|
|
7658
7893
|
|
|
7659
7894
|
## Approving from the CLI
|
|
7660
7895
|
|
|
@@ -7676,10 +7911,23 @@ kici reject <run-id> --job deploy-production --reason "Wrong release branch"
|
|
|
7676
7911
|
|
|
7677
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.md#kici-approve) for the full command reference.
|
|
7678
7913
|
|
|
7914
|
+
### Inline approval and `--approve-all` in `kici run remote`
|
|
7915
|
+
|
|
7916
|
+
When you trigger a run with `kici run remote` and it pauses on a gate, the CLI prints the gate (and, for a drift gate, the computed drift) and — in an interactive terminal — prompts you to approve or reject right there, without leaving the watch. In a non-interactive shell it prints how to approve out of band and keeps watching.
|
|
7917
|
+
|
|
7918
|
+
To auto-approve **every gate of the run you just dispatched**, pass `--approve-all` (alias `--yes`):
|
|
7919
|
+
|
|
7920
|
+
```bash
|
|
7921
|
+
kici run remote deploy-prod --approve-all
|
|
7922
|
+
```
|
|
7923
|
+
|
|
7924
|
+
`--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
|
+
|
|
7679
7926
|
You can also approve from the dashboard approval queue. See [Dashboard](dashboard/environments-and-secrets.md#approval-queue).
|
|
7680
7927
|
|
|
7681
7928
|
## See also
|
|
7682
7929
|
|
|
7930
|
+
- [Idempotent steps](idempotent-steps.md) — the check/apply step facet that drift gates build on.
|
|
7683
7931
|
- [Environments](environments.md) — operator-required reviewers on protected environments.
|
|
7684
7932
|
- [Approval gates (operator guide)](../operator/approvals.md) — teams, the approval queue, expiry, and self-approval.
|
|
7685
7933
|
- [Approval gates (architecture)](../architecture/approvals.md) — the unified hold model and the step-level round-trip.
|
|
@@ -8312,7 +8560,7 @@ Required reviewers: alice, bob
|
|
|
8312
8560
|
|
|
8313
8561
|
When reviewers are required, the job enters a "held" state. Reviewers can approve or reject via the dashboard, the [`kici approve`](cli-reference.md#kici-approve) command, or the API. Held runs expire after a configurable timeout.
|
|
8314
8562
|
|
|
8315
|
-
This operator-set rule is the **mandatory** form of an approval gate. Workflow authors can also declare gates in code with `
|
|
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.md). Both forms use the same held-element mechanism and the same queue.
|
|
8316
8564
|
|
|
8317
8565
|
### Wait timer
|
|
8318
8566
|
|
|
@@ -8987,7 +9235,7 @@ const configureNginx = step('configure-nginx', {
|
|
|
8987
9235
|
`summarize` is **required** whenever `check` is declared. `run` and `whenInSync`
|
|
8988
9236
|
both produce the same output type — one output shape per step, whichever path
|
|
8989
9237
|
runs. Every other step facet (`cache`, `rules`, `continueOnError`, `timeout`,
|
|
8990
|
-
`
|
|
9238
|
+
`approval`, `onCancel`, `cleanup`, `outputs`) composes unchanged.
|
|
8991
9239
|
|
|
8992
9240
|
A plain `step()` without `check` keeps its exact current behavior — the check
|
|
8993
9241
|
facet is fully optional.
|
|
@@ -9886,11 +10134,12 @@ GitHub App "my-org" is live.
|
|
|
9886
10134
|
|
|
9887
10135
|
**Flags:**
|
|
9888
10136
|
|
|
9889
|
-
| Flag | Effect
|
|
9890
|
-
| --------------------- |
|
|
9891
|
-
| `--name <name>` | The App name on GitHub (required).
|
|
9892
|
-
| `--github-org <slug>` | Create the App under a GitHub organization instead of your personal account.
|
|
9893
|
-
| `--
|
|
10137
|
+
| Flag | Effect |
|
|
10138
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
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](#display-name-and-slug)). |
|
|
10140
|
+
| `--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](#self-hosted-webhook-url-override). |
|
|
10142
|
+
| `--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. |
|
|
9894
10143
|
|
|
9895
10144
|
The manifest flow always creates a **new** App on GitHub. If a source for
|
|
9896
10145
|
that App id already exists on the orchestrator, the command refuses — use
|
|
@@ -10054,6 +10303,53 @@ its own source record and the orchestrator looks up the right one by
|
|
|
10054
10303
|
combining the URL's `<orgId>` with the App ID from the
|
|
10055
10304
|
`X-GitHub-Hook-Installation-Target-ID` header.
|
|
10056
10305
|
|
|
10306
|
+
## Display name and slug
|
|
10307
|
+
|
|
10308
|
+
For a GitHub App source, **GitHub is the source of truth for the displayed
|
|
10309
|
+
name**. `--name` is only the name you _request_ when the App is created;
|
|
10310
|
+
GitHub assigns the final display name and a URL-safe **slug**
|
|
10311
|
+
(`my-org` → `my-org-1` if the name was taken). KiCI captures both at creation
|
|
10312
|
+
and shows them in the dashboard **Sources** tab — the display name prominently,
|
|
10313
|
+
with the slug as dimmed secondary text.
|
|
10314
|
+
|
|
10315
|
+
If you later rename the App in GitHub's UI, KiCI keeps the displayed name in
|
|
10316
|
+
sync two ways:
|
|
10317
|
+
|
|
10318
|
+
- **Automatically**, on a daily schedule. The orchestrator re-reads each GitHub
|
|
10319
|
+
source's name + slug from GitHub and updates the dashboard if they changed.
|
|
10320
|
+
The interval is configurable via `KICI_GITHUB_APP_NAME_REFRESH_INTERVAL_MS`
|
|
10321
|
+
(default 24h).
|
|
10322
|
+
- **On demand**, with `source refresh`:
|
|
10323
|
+
|
|
10324
|
+
```bash
|
|
10325
|
+
kici-admin source refresh github:<appId> # one source
|
|
10326
|
+
kici-admin source refresh --all # every GitHub source
|
|
10327
|
+
```
|
|
10328
|
+
|
|
10329
|
+
It prints `old → new` for any name or slug that changed, and is a no-op when
|
|
10330
|
+
GitHub already matches what KiCI has stored. Non-GitHub routing keys are
|
|
10331
|
+
rejected — name/slug sync applies only to GitHub App sources.
|
|
10332
|
+
|
|
10333
|
+
## Self-hosted webhook URL override
|
|
10334
|
+
|
|
10335
|
+
By default the manifest flow bakes the KiCI Platform webhook endpoint
|
|
10336
|
+
(`https://<platform-host>/webhook/<orgId>/github`) into the App. If you run
|
|
10337
|
+
your own ingress and want GitHub to deliver events to it instead, pass
|
|
10338
|
+
`--webhook-url` when creating the App:
|
|
10339
|
+
|
|
10340
|
+
```bash
|
|
10341
|
+
kici-admin source add github --manifest --name my-org \
|
|
10342
|
+
--webhook-url https://hooks.my-infra.example/github
|
|
10343
|
+
```
|
|
10344
|
+
|
|
10345
|
+
The supplied URL must be an absolute `https://` URL; it is written into the
|
|
10346
|
+
App's webhook configuration **verbatim**. This is the operator asserting "I own
|
|
10347
|
+
webhook delivery": KiCI adds **no** ingress at this URL and does **not** receive
|
|
10348
|
+
events there — your own infrastructure is responsible for accepting GitHub's
|
|
10349
|
+
deliveries and routing them onward. Supplying the flag also decouples App
|
|
10350
|
+
creation from platform-mode URL resolution, so it works even in a configuration
|
|
10351
|
+
where the auto-resolved KiCI Platform URL is unavailable.
|
|
10352
|
+
|
|
10057
10353
|
## Global workflows
|
|
10058
10354
|
|
|
10059
10355
|
A GitHub App source opts in to org-wide global workflows using the
|
|
@@ -14,6 +14,7 @@ The full markdown bundle of every page indexed here is available at https://docs
|
|
|
14
14
|
|
|
15
15
|
- [Basic workflow patterns](https://docs.kici.dev/user/patterns/basic/): Basic CI, PR-only / push-only filters, multiple triggers, manual-only workflows
|
|
16
16
|
- [Conditionals & matrix patterns](https://docs.kici.dev/user/patterns/conditionals-matrix/): Conditional execution with rules, matrix builds (static + dynamic), dynamic job generation
|
|
17
|
+
- [Host restart & wait-for-alive](https://docs.kici.dev/user/patterns/host-restart/): Reboot the host a workflow runs on and continue after it comes back
|
|
17
18
|
- [Integration patterns](https://docs.kici.dev/user/patterns/integrations/): Workflow chaining, generic webhooks, Stripe, self-hosted git forges, plain GitHub repos
|
|
18
19
|
- [Pattern reference](https://docs.kici.dev/user/patterns/reference/): Step context, examples repository, GitHub check run output — cross-cutting reference for all patterns
|
|
19
20
|
- [Scheduling & event patterns](https://docs.kici.dev/user/patterns/scheduling-and-events/): Nightly cron, workflow-complete-triggered deploys, custom event chaining
|
|
@@ -44,7 +45,7 @@ The full markdown bundle of every page indexed here is available at https://docs
|
|
|
44
45
|
## Workflow features
|
|
45
46
|
|
|
46
47
|
- [Account and sign-in](https://docs.kici.dev/user/account-and-login/): How your KiCI account relates to sign-in methods, and how to change the way you sign in.
|
|
47
|
-
- [Approval gates](https://docs.kici.dev/user/approvals/): Pause a workflow for human sign-off at step, job, or workflow granularity with
|
|
48
|
+
- [Approval gates](https://docs.kici.dev/user/approvals/): Pause a workflow for human sign-off at step, job, or workflow granularity with approval
|
|
48
49
|
- [Concurrency groups](https://docs.kici.dev/user/concurrency/): Control parallel execution with auto-cancel and queue modes
|
|
49
50
|
- [Dashboard](https://docs.kici.dev/user/dashboard/): Web UI for monitoring workflow runs, managing sources, secrets, and organization settings.
|
|
50
51
|
- [Dynamic values](https://docs.kici.dev/user/dynamic-values/)
|