@kici-dev/compiler 0.1.26 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/headless-detect.d.ts +14 -5
- package/dist/auth/headless-detect.js +50 -9
- package/dist/cli-banner.d.ts +6 -3
- package/dist/cli-banner.js +13 -5
- package/dist/cli.js +180 -48
- package/dist/commands/check-mode.d.ts +1 -1
- package/dist/commands/compile.js +31 -7
- package/dist/commands/diagnostics.js +14 -2
- package/dist/commands/doctor.d.ts +64 -0
- package/dist/commands/doctor.js +413 -0
- package/dist/commands/endpoints.js +6 -0
- package/dist/commands/fixture.js +13 -0
- package/dist/commands/hook.js +3 -2
- package/dist/commands/index.d.ts +13 -1
- package/dist/commands/index.js +10 -3
- package/dist/commands/init-host-os.d.ts +34 -0
- package/dist/commands/init-host-os.js +44 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +160 -37
- package/dist/commands/local-trust-root.d.ts +6 -0
- package/dist/commands/local-trust-root.js +52 -0
- package/dist/commands/local.d.ts +65 -0
- package/dist/commands/local.js +206 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +41 -1
- package/dist/commands/logout.js +7 -0
- package/dist/commands/notifications.d.ts +61 -0
- package/dist/commands/notifications.js +274 -0
- package/dist/commands/preview.js +9 -2
- package/dist/commands/run-banner.d.ts +31 -0
- package/dist/commands/run-banner.js +25 -0
- package/dist/commands/run-routed.d.ts +53 -0
- package/dist/commands/run-routed.js +185 -0
- package/dist/commands/run.d.ts +0 -17
- package/dist/commands/run.js +44 -58
- package/dist/commands/runs/artifacts/download.d.ts +24 -0
- package/dist/commands/runs/artifacts/download.js +154 -0
- package/dist/commands/runs/artifacts/list.d.ts +5 -0
- package/dist/commands/runs/artifacts/list.js +47 -0
- package/dist/commands/runs/cancel.js +2 -2
- package/dist/commands/runs/list.js +8 -2
- package/dist/commands/secrets-list.d.ts +2 -2
- package/dist/commands/secrets-list.js +4 -4
- package/dist/commands/types.d.ts +3 -3
- package/dist/commands/types.js +4 -4
- package/dist/commands/verify-attestation.d.ts +3 -1
- package/dist/commands/verify-attestation.js +18 -7
- package/dist/errors/formatter.d.ts +14 -1
- package/dist/errors/formatter.js +13 -3
- package/dist/errors/index.d.ts +2 -1
- package/dist/errors/index.js +3 -2
- package/dist/errors/source-location.d.ts +20 -0
- package/dist/errors/source-location.js +45 -0
- package/dist/execution/executor.js +32 -12
- package/dist/fixtures/defaults/index.js +2 -1
- package/dist/generators/secrets-dts.d.ts +9 -9
- package/dist/generators/secrets-dts.js +12 -12
- package/dist/llm-context/llms-architecture.txt +50 -21
- package/dist/llm-context/llms-cli.txt +2667 -1454
- package/dist/llm-context/llms-features.txt +484 -414
- package/dist/llm-context/llms-full.txt +6703 -4687
- package/dist/llm-context/llms-getting-started.txt +311 -55
- package/dist/llm-context/llms-patterns.txt +17 -29
- package/dist/llm-context/llms-providers.txt +10 -1
- package/dist/llm-context/llms-sdk-runtime.txt +2013 -0
- package/dist/llm-context/llms-sdk.txt +596 -2157
- package/dist/llm-context/llms.txt +31 -17
- package/dist/local-plane/orchestrator-process.d.ts +70 -0
- package/dist/local-plane/orchestrator-process.js +129 -0
- package/dist/local-plane/os-mismatch-hint.d.ts +16 -0
- package/dist/local-plane/os-mismatch-hint.js +34 -0
- package/dist/local-plane/paths.d.ts +39 -0
- package/dist/local-plane/paths.js +54 -0
- package/dist/local-plane/plane-liveness.d.ts +68 -0
- package/dist/local-plane/plane-liveness.js +145 -0
- package/dist/local-plane/plane-manager.d.ts +193 -0
- package/dist/local-plane/plane-manager.js +480 -0
- package/dist/local-plane/plane-seed.d.ts +76 -0
- package/dist/local-plane/plane-seed.js +59 -0
- package/dist/local-plane/plane-trigger.d.ts +71 -0
- package/dist/local-plane/plane-trigger.js +110 -0
- package/dist/local-plane/platform-attach.d.ts +53 -0
- package/dist/local-plane/platform-attach.js +107 -0
- package/dist/local-plane/port-holder.d.ts +78 -0
- package/dist/local-plane/port-holder.js +227 -0
- package/dist/local-plane/postgres.d.ts +56 -0
- package/dist/local-plane/postgres.js +187 -0
- package/dist/local-plane/resolve-plane.d.ts +37 -0
- package/dist/local-plane/resolve-plane.js +76 -0
- package/dist/local-plane/run-follow.d.ts +61 -0
- package/dist/local-plane/run-follow.js +151 -0
- package/dist/local-plane/scaler-config.d.ts +68 -0
- package/dist/local-plane/scaler-config.js +132 -0
- package/dist/local-plane/secret-seed.d.ts +59 -0
- package/dist/local-plane/secret-seed.js +112 -0
- package/dist/local-plane/source-provider.d.ts +43 -0
- package/dist/local-plane/source-provider.js +159 -0
- package/dist/local-plane/trusted-routing.d.ts +32 -0
- package/dist/local-plane/trusted-routing.js +47 -0
- package/dist/lockfile/generator.d.ts +14 -1
- package/dist/lockfile/generator.js +103 -37
- package/dist/lockfile/index.d.ts +3 -1
- package/dist/lockfile/index.js +3 -2
- package/dist/lockfile/purity-diagnostics.d.ts +31 -0
- package/dist/lockfile/purity-diagnostics.js +52 -0
- package/dist/postinstall.js +2 -1
- package/dist/remote/artifact-extract.d.ts +8 -0
- package/dist/remote/artifact-extract.js +58 -0
- package/dist/remote/config.d.ts +21 -3
- package/dist/remote/config.js +24 -5
- package/dist/remote/dashboard-client.d.ts +46 -9
- package/dist/remote/dashboard-client.js +54 -11
- package/dist/remote/fs-case.d.ts +21 -0
- package/dist/remote/fs-case.js +45 -0
- package/dist/remote/history.d.ts +9 -3
- package/dist/remote/history.js +79 -10
- package/dist/remote/local-repo-identity.d.ts +1 -1
- package/dist/remote/local-repo-identity.js +2 -1
- package/dist/remote/notifications-client.d.ts +149 -0
- package/dist/remote/notifications-client.js +103 -0
- package/dist/remote/oauth.d.ts +13 -0
- package/dist/remote/oauth.js +31 -5
- package/dist/remote/output/summary.js +76 -14
- package/dist/remote/platform-client.d.ts +1 -1
- package/dist/remote/render.js +30 -9
- package/dist/{local-executor → remote}/secret-loader.d.ts +1 -1
- package/dist/{local-executor → remote}/secret-loader.js +4 -4
- package/dist/remote/secret-upload.d.ts +1 -1
- package/dist/remote/secret-upload.js +2 -2
- package/dist/remote/uploader.d.ts +1 -1
- package/dist/remote/uploader.js +7 -6
- package/dist/templates/agents-md.d.ts +1 -1
- package/dist/templates/agents-md.js +8 -8
- package/dist/templates/index.d.ts +1 -1
- package/dist/templates/index.js +2 -2
- package/dist/templates/package-json.d.ts +15 -0
- package/dist/templates/package-json.js +26 -5
- package/dist/test-runner/dry-run.d.ts +2 -1
- package/dist/test-runner/dry-run.js +18 -1
- package/dist/test-runner/event-types.d.ts +2 -0
- package/dist/test-runner/event-types.js +3 -0
- package/dist/test-runner/job-executor.d.ts +1 -0
- package/dist/test-runner/job-executor.js +82 -44
- package/dist/test-runner/output-formatter.d.ts +5 -2
- package/dist/test-runner/output-formatter.js +6 -4
- package/dist/test-runner/payload-builder.d.ts +7 -1
- package/dist/test-runner/payload-builder.js +11 -2
- package/dist/test-runner/rule-evaluator.d.ts +7 -3
- package/dist/test-runner/rule-evaluator.js +13 -12
- package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
- package/dist/test-runner/step-context.d.ts +23 -5
- package/dist/test-runner/step-context.js +29 -119
- package/dist/types.d.ts +26 -7
- package/dist/types.js +4 -2
- package/dist/validation/index.d.ts +2 -0
- package/dist/validation/index.js +2 -1
- package/dist/validation/typecheck.d.ts +28 -0
- package/dist/validation/typecheck.js +110 -0
- package/dist/validation/validator.d.ts +9 -4
- package/dist/validation/validator.js +38 -44
- package/package.json +13 -13
- package/sbom.spdx.json +9311 -1528
- package/dist/assets/api-TJJVHrjC.json +0 -118
- package/dist/assets/descriptor-BTtjzN9L.json +0 -1382
- package/dist/assets/package-BpQF9kR8.json +0 -74
- package/dist/assets/package-Ceo2h27X.json +0 -89
- package/dist/assets/source_context-D0atuL28.json +0 -20
- package/dist/assets/type-BFqO8SCZ.json +0 -202
- package/dist/commands/cancel.d.ts +0 -22
- package/dist/commands/detect-package-manager.d.ts +0 -42
- package/dist/commands/held-run-resolve.d.ts +0 -50
- package/dist/commands/status.d.ts +0 -34
- package/dist/commands/test.d.ts +0 -88
- package/dist/errors/codes.d.ts +0 -25
- package/dist/local-executor/dag-scheduler.d.ts +0 -44
- package/dist/local-executor/dag-scheduler.js +0 -183
- package/dist/local-executor/index.d.ts +0 -23
- package/dist/local-executor/index.js +0 -378
- package/dist/local-executor/job-runner.d.ts +0 -47
- package/dist/local-executor/job-runner.js +0 -411
- package/dist/local-executor/materializer.d.ts +0 -44
- package/dist/local-executor/materializer.js +0 -132
- package/dist/local-executor/output-streamer.d.ts +0 -31
- package/dist/local-executor/output-streamer.js +0 -168
- package/dist/local-executor/payload-generator.d.ts +0 -16
- package/dist/local-executor/payload-generator.js +0 -138
- package/dist/local-executor/picker.d.ts +0 -33
- package/dist/local-executor/picker.js +0 -109
- package/dist/local-executor/to-event-payload.d.ts +0 -16
- package/dist/local-executor/to-event-payload.js +0 -21
- package/dist/local-executor/types.d.ts +0 -93
- package/dist/local-executor/types.js +0 -2
- package/dist/local-executor/workflow-lock.d.ts +0 -82
- package/dist/local-executor/workflow-lock.js +0 -0
- package/dist/package-F7UXSDHW.json +0 -74
- package/dist/remote/client.d.ts +0 -210
- package/dist/remote/observer.d.ts +0 -81
- package/dist/test-runner/summary.d.ts +0 -7
- /package/dist/{local-executor → test-runner}/runs-on-display.d.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# KiCI Workflow features
|
|
2
2
|
|
|
3
|
-
This bundle covers: Workflow features: concurrency,
|
|
3
|
+
This bundle covers: Workflow features: concurrency, contexts, secrets, approvals, provenance, events.
|
|
4
4
|
|
|
5
5
|
## Account and sign-in
|
|
6
6
|
|
|
@@ -101,13 +101,13 @@ When the run reaches this job, it is held instead of dispatched. The held run ap
|
|
|
101
101
|
job('deploy', {
|
|
102
102
|
runsOn: 'default',
|
|
103
103
|
approval: true,
|
|
104
|
-
steps: [
|
|
105
|
-
/* ... */
|
|
106
|
-
],
|
|
104
|
+
steps: [/* ... */],
|
|
107
105
|
});
|
|
108
106
|
```
|
|
109
107
|
|
|
110
|
-
`approval: true` holds the element until **any** org member who can act on approvals signs off — anyone with
|
|
108
|
+
`approval: true` holds the element until **any** org member who can act on approvals signs off — anyone with `contexts:write`, since an `approval` gate always raises a reviewer hold. Use it when you want a manual gate without restricting who may release it.
|
|
109
|
+
|
|
110
|
+
A **security** hold is different: it is raised by the CI trust pipeline (an unknown contributor, a fork PR, a workflow-modifying PR), never by `approval`, and releasing one requires `ci_trust:write`. See [Approval gates (operator guide)](https://docs.kici.dev/operator/approvals/#who-may-approve).
|
|
111
111
|
|
|
112
112
|
### Approver list (AND)
|
|
113
113
|
|
|
@@ -135,15 +135,17 @@ approval: {
|
|
|
135
135
|
},
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
| Field | Type | Description
|
|
139
|
-
| ----------- | --------------------- |
|
|
140
|
-
| `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).
|
|
141
|
-
| `approvers` | `ApproverClause[]` | The AND list of `{ team }` / `{ user }` clauses. An empty list means "any approval-capable member".
|
|
142
|
-
| `reason` | `string` | A human-readable label shown in the dashboard queue and the held-for-approval status check.
|
|
143
|
-
| `timeout` | `number` | Per-gate expiry in **seconds**, overriding the org default. On expiry the element is rejected.
|
|
138
|
+
| Field | Type | Description |
|
|
139
|
+
| ----------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
+
| `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). |
|
|
141
|
+
| `approvers` | `ApproverClause[]` | The AND list of `{ team }` / `{ user }` clauses. An empty list means "any approval-capable member". |
|
|
142
|
+
| `reason` | `string` | A human-readable label shown in the dashboard queue and the held-for-approval status check. |
|
|
143
|
+
| `timeout` | `number` | Per-gate expiry in **seconds**, overriding the org default. Must be a **positive integer** number of seconds; a non-positive or non-finite value is rejected at compile time. On expiry the element is rejected. |
|
|
144
144
|
|
|
145
145
|
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).
|
|
146
146
|
|
|
147
|
+
A `timeout` that is zero, negative, or non-finite (for example a computed `minutes * 60` where `minutes` is `0`) fails `kici compile` with a clear author-facing error, so a misconfigured gate can never silently expire the moment it is created. If an orchestrator ever receives such a value from a hand-edited lock file, the run fails fast with an **Approval gate misconfigured** init-failure rather than dispatching ungated.
|
|
148
|
+
|
|
147
149
|
## Granularity
|
|
148
150
|
|
|
149
151
|
The same `approval` field is accepted on a workflow, a job, and a step.
|
|
@@ -168,9 +170,7 @@ A job-level gate holds just that job; other jobs in the run proceed normally:
|
|
|
168
170
|
job('publish', {
|
|
169
171
|
runsOn: 'default',
|
|
170
172
|
approval: [{ team: 'leads' }],
|
|
171
|
-
steps: [
|
|
172
|
-
/* ... */
|
|
173
|
-
],
|
|
173
|
+
steps: [/* ... */],
|
|
174
174
|
});
|
|
175
175
|
```
|
|
176
176
|
|
|
@@ -228,7 +228,7 @@ A `when: 'drift'` gate on a step without a `check` facet, or at job/workflow sco
|
|
|
228
228
|
|
|
229
229
|
## Mandatory vs. explicit gates
|
|
230
230
|
|
|
231
|
-
`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
|
|
231
|
+
`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 context via required reviewers (see [Contexts](https://docs.kici.dev/user/contexts/#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.
|
|
232
232
|
|
|
233
233
|
## Approving from the CLI
|
|
234
234
|
|
|
@@ -241,14 +241,14 @@ kici approve <run-id>
|
|
|
241
241
|
# Approve a held job
|
|
242
242
|
kici approve <run-id> --job deploy-production
|
|
243
243
|
|
|
244
|
-
# Approve a held step
|
|
245
|
-
kici approve <run-id> --job migrate-and-deploy --step
|
|
244
|
+
# Approve a held step (--step takes the step's zero-based index within the job)
|
|
245
|
+
kici approve <run-id> --job migrate-and-deploy --step 1
|
|
246
246
|
|
|
247
247
|
# Reject (a reason is required)
|
|
248
248
|
kici reject <run-id> --job deploy-production --reason "Wrong release branch"
|
|
249
249
|
```
|
|
250
250
|
|
|
251
|
-
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-
|
|
251
|
+
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/runs-and-approvals/#kici-approve) for the full command reference.
|
|
252
252
|
|
|
253
253
|
### Inline approval and `--approve-all` in `kici run remote`
|
|
254
254
|
|
|
@@ -262,12 +262,14 @@ kici run remote deploy-prod --approve-all
|
|
|
262
262
|
|
|
263
263
|
`--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`).
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
`--approve-all` is honored in non-interactive runs too: with `--json` or `--quiet` (no TTY), the flag still auto-approves each eligible gate as it appears instead of hanging or printing out-of-band instructions. Hold notices are routed to stderr so `--json` stdout stays pure machine-readable output.
|
|
266
|
+
|
|
267
|
+
You can also approve from the dashboard approval queue. See [Dashboard](https://docs.kici.dev/user/dashboard/contexts-and-secrets/#approval-queue).
|
|
266
268
|
|
|
267
269
|
## See also
|
|
268
270
|
|
|
269
271
|
- [Idempotent steps](https://docs.kici.dev/user/idempotent-steps/) — the check/apply step facet that drift gates build on.
|
|
270
|
-
- [
|
|
272
|
+
- [Contexts](https://docs.kici.dev/user/contexts/) — operator-required reviewers on protected contexts.
|
|
271
273
|
- [Approval gates (operator guide)](https://docs.kici.dev/operator/approvals/) — teams, the approval queue, expiry, and self-approval.
|
|
272
274
|
- [Approval gates (architecture)](https://docs.kici.dev/architecture/approvals/) — the unified hold model and the step-level round-trip.
|
|
273
275
|
|
|
@@ -329,7 +331,7 @@ group: () => 'deploy';
|
|
|
329
331
|
group: (ctx) => `deploy-${ctx.event.targetBranch ?? 'default'}`;
|
|
330
332
|
```
|
|
331
333
|
|
|
332
|
-
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 [
|
|
334
|
+
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 [Contexts](https://docs.kici.dev/user/contexts/#concurrency-groups)), where the compiler performs purity analysis and can inline pure functions for orchestrator-side evaluation.
|
|
333
335
|
|
|
334
336
|
## cancelInProgress mode
|
|
335
337
|
|
|
@@ -355,9 +357,7 @@ workflow('deploy', {
|
|
|
355
357
|
group: (ctx) => `deploy-${ctx.branch}`,
|
|
356
358
|
cancelInProgress: true,
|
|
357
359
|
},
|
|
358
|
-
jobs: [
|
|
359
|
-
/* ... */
|
|
360
|
-
],
|
|
360
|
+
jobs: [/* ... */],
|
|
361
361
|
});
|
|
362
362
|
```
|
|
363
363
|
|
|
@@ -381,9 +381,7 @@ workflow('migrate-db', {
|
|
|
381
381
|
cancelInProgress: false,
|
|
382
382
|
max: 1,
|
|
383
383
|
},
|
|
384
|
-
jobs: [
|
|
385
|
-
/* ... */
|
|
386
|
-
],
|
|
384
|
+
jobs: [/* ... */],
|
|
387
385
|
});
|
|
388
386
|
```
|
|
389
387
|
|
|
@@ -401,9 +399,7 @@ workflow('test', {
|
|
|
401
399
|
cancelInProgress: false,
|
|
402
400
|
max: 3,
|
|
403
401
|
},
|
|
404
|
-
jobs: [
|
|
405
|
-
/* ... */
|
|
406
|
-
],
|
|
402
|
+
jobs: [/* ... */],
|
|
407
403
|
});
|
|
408
404
|
```
|
|
409
405
|
|
|
@@ -422,10 +418,8 @@ workflow('deploy', {
|
|
|
422
418
|
jobs: [
|
|
423
419
|
job('deploy-staging', {
|
|
424
420
|
runsOn: 'linux',
|
|
425
|
-
|
|
426
|
-
steps: [
|
|
427
|
-
/* ... */
|
|
428
|
-
],
|
|
421
|
+
context: 'staging',
|
|
422
|
+
steps: [/* ... */],
|
|
429
423
|
}),
|
|
430
424
|
],
|
|
431
425
|
});
|
|
@@ -440,9 +434,7 @@ workflow('migrate', {
|
|
|
440
434
|
group: () => 'db-migration',
|
|
441
435
|
cancelInProgress: false,
|
|
442
436
|
},
|
|
443
|
-
jobs: [
|
|
444
|
-
/* ... */
|
|
445
|
-
],
|
|
437
|
+
jobs: [/* ... */],
|
|
446
438
|
});
|
|
447
439
|
```
|
|
448
440
|
|
|
@@ -458,17 +450,15 @@ workflow('deploy', {
|
|
|
458
450
|
},
|
|
459
451
|
cancelInProgress: true,
|
|
460
452
|
},
|
|
461
|
-
jobs: [
|
|
462
|
-
/* ... */
|
|
463
|
-
],
|
|
453
|
+
jobs: [/* ... */],
|
|
464
454
|
});
|
|
465
455
|
```
|
|
466
456
|
|
|
467
|
-
## Interaction with
|
|
457
|
+
## Interaction with context protection
|
|
468
458
|
|
|
469
|
-
When a workflow has both `concurrency` and `
|
|
459
|
+
When a workflow has both `concurrency` and `context` protection rules:
|
|
470
460
|
|
|
471
|
-
1.
|
|
461
|
+
1. Context protection gates (required reviewers, wait timer) apply first
|
|
472
462
|
2. Concurrency group check happens after protection gates pass
|
|
473
463
|
3. If the run is queued by concurrency, it keeps its protection approval
|
|
474
464
|
|
|
@@ -480,17 +470,15 @@ Queued runs can be cancelled before they start executing. The cancel request rem
|
|
|
480
470
|
|
|
481
471
|
## Job-level concurrency groups
|
|
482
472
|
|
|
483
|
-
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 [
|
|
473
|
+
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 [Contexts — concurrency groups](https://docs.kici.dev/user/contexts/#concurrency-groups) for details.
|
|
484
474
|
|
|
485
475
|
## Local execution
|
|
486
476
|
|
|
487
|
-
`kici run local`
|
|
488
|
-
|
|
489
|
-
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.
|
|
477
|
+
`kici run <event> --local` is a real routed dispatch: your machine becomes an ephemeral agent behind the local dev plane, whose own orchestrator applies the same concurrency machinery described above. The `group` callback is evaluated agent-side against the simulated event, and `cancelInProgress` carries its usual semantics — `true` supersedes the older run in the group, `false` queues the newer one behind it.
|
|
490
478
|
|
|
491
|
-
|
|
479
|
+
Coordination is scoped to that plane. The plane's state (including its database) lives under `~/.kici/local/`, so enforcement is per-machine and per-user: running the same workflow on two different machines does not serialize across them. For cross-host enforcement (queueing across agents, dashboard visibility), use `kici run remote` against a deployed orchestrator.
|
|
492
480
|
|
|
493
|
-
|
|
481
|
+
See [`kici run <event> --local`](https://docs.kici.dev/user/cli/runs-and-approvals/#kici-run-event---local) for the rest of the local-run behavior, and [the local dev plane](https://docs.kici.dev/operator/orchestrator/local-dev-plane/) for the plane's state directory and lifecycle.
|
|
494
482
|
|
|
495
483
|
---
|
|
496
484
|
|
|
@@ -498,239 +486,19 @@ _Source: `packages/sdk/src/types.ts` (WorkflowOptions.concurrency, JobOptions.co
|
|
|
498
486
|
|
|
499
487
|
---
|
|
500
488
|
|
|
501
|
-
##
|
|
502
|
-
|
|
503
|
-
Source: https://docs.kici.dev/user/dashboard/
|
|
504
|
-
|
|
505
|
-
The KiCI dashboard is the browser interface for monitoring workflow runs, inspecting jobs and logs, and managing your organization. It signs in via OIDC and talks to KiCI over its API.
|
|
506
|
-
|
|
507
|
-
This guide is split across the following pages:
|
|
508
|
-
|
|
509
|
-
| Page | Covers |
|
|
510
|
-
| ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
511
|
-
| [Getting started](https://docs.kici.dev/user/dashboard/getting-started/) | Onboarding checklist and your organizations list |
|
|
512
|
-
| [Navigation and layout](https://docs.kici.dev/user/dashboard/navigation/) | Sidebar, mobile nav, theme, time display, shortcuts, error pages |
|
|
513
|
-
| [Runs and logs](https://docs.kici.dev/user/dashboard/runs/) | Run list, run detail, the log viewer |
|
|
514
|
-
| [Settings](https://docs.kici.dev/user/dashboard/settings/) | Members, roles, teams, keys, sources, billing, security, support access |
|
|
515
|
-
| [Workflows, diagnostics, and orchestrators](https://docs.kici.dev/user/dashboard/workflows-and-diagnostics/) | Registered workflows, infra health, per-cluster views |
|
|
516
|
-
| [Environments, secrets, and approvals](https://docs.kici.dev/user/dashboard/environments-and-secrets/) | Environments, secret scopes, approval queue |
|
|
517
|
-
| [Activity and DLQ](https://docs.kici.dev/user/dashboard/activity-and-dlq/) | Forensic activity log and dead-letter queue |
|
|
518
|
-
| [Account](https://docs.kici.dev/user/dashboard/account/) | Personal account settings |
|
|
519
|
-
|
|
520
|
-
---
|
|
521
|
-
|
|
522
|
-
## Dynamic values
|
|
523
|
-
|
|
524
|
-
Source: https://docs.kici.dev/user/dynamic-values/
|
|
525
|
-
|
|
526
|
-
Dynamic values let you compute `environment`, `env`, and `concurrencyGroup` at runtime based on the incoming event. Instead of hardcoding static strings, you pass a function that receives the normalized event envelope and returns the resolved value.
|
|
527
|
-
|
|
528
|
-
```typescript
|
|
529
|
-
job('deploy', {
|
|
530
|
-
runsOn: ['default'],
|
|
531
|
-
environment: (event) => event.targetBranch,
|
|
532
|
-
env: (event) => ({ BRANCH: event.targetBranch }),
|
|
533
|
-
concurrencyGroup: (event) => `deploy-${event.targetBranch}`,
|
|
534
|
-
steps: [
|
|
535
|
-
/* ... */
|
|
536
|
-
],
|
|
537
|
-
});
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
```typescript
|
|
541
|
-
job('deploy', {
|
|
542
|
-
runsOn: 'default',
|
|
543
|
-
// One shape everywhere: branch on the normalized event type.
|
|
544
|
-
environment: (event) => (event.type === 'pull_request' ? 'preview' : 'production'),
|
|
545
|
-
steps: [
|
|
546
|
-
/* ... */
|
|
547
|
-
],
|
|
548
|
-
});
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
## How it works
|
|
552
|
-
|
|
553
|
-
When you define a dynamic value as a function, the compiler analyzes it at compile time to determine whether it is **pure** (can be evaluated without cloning the repo or running an init job).
|
|
554
|
-
|
|
555
|
-
### Pure functions (inline evaluation)
|
|
556
|
-
|
|
557
|
-
A pure function is one that:
|
|
558
|
-
|
|
559
|
-
- Is synchronous (no `async`/`await`)
|
|
560
|
-
- Only references its parameters and local variables
|
|
561
|
-
- Does not import or require external modules
|
|
562
|
-
- Does not access globals like `process`, `fetch`, `console`, `setTimeout`, etc.
|
|
563
|
-
- Uses only safe built-in constructors: `String`, `Number`, `Boolean`, `Array`, `Object`, `JSON`, `Math`, `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `encodeURIComponent`, `decodeURIComponent`, `encodeURI`, `decodeURI`
|
|
564
|
-
- Does not use `this`, `new`, `class`, `throw`, `try`/`catch`, `delete`, `var`, `yield`, or mutation operators (`++`, `--`, `+=`, etc.)
|
|
565
|
-
|
|
566
|
-
When the compiler detects a pure function, it serializes the function source directly into the lock file as an inline expression. At dispatch time, the orchestrator evaluates the expression in a sandboxed VM context (~0ms overhead) instead of dispatching an init job.
|
|
567
|
-
|
|
568
|
-
**Examples of pure functions:**
|
|
569
|
-
|
|
570
|
-
```typescript
|
|
571
|
-
// Simple branch extraction
|
|
572
|
-
environment: (event) => event.targetBranch;
|
|
573
|
-
|
|
574
|
-
// Object literal with string operations
|
|
575
|
-
env: (event) => ({ BRANCH: event.targetBranch });
|
|
576
|
-
|
|
577
|
-
// Concatenation with event data
|
|
578
|
-
concurrencyGroup: (event) => `deploy-${event.targetBranch}`;
|
|
579
|
-
|
|
580
|
-
// Using safe globals
|
|
581
|
-
env: (event) => ({ UPPER: String(event.targetBranch).toUpperCase() });
|
|
582
|
-
|
|
583
|
-
// Local variables are fine
|
|
584
|
-
environment: (event) => {
|
|
585
|
-
const parts = event.targetBranch.split('/');
|
|
586
|
-
return parts[parts.length - 1];
|
|
587
|
-
};
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
### Impure functions (init-job evaluation)
|
|
591
|
-
|
|
592
|
-
If the compiler determines a function is impure, it emits a warning during compilation and falls back to the two-phase init model. This means:
|
|
593
|
-
|
|
594
|
-
1. The orchestrator dispatches a special `__init__` job to a builder agent
|
|
595
|
-
2. The builder agent clones the repository and evaluates the function
|
|
596
|
-
3. The resolved values are sent back to the orchestrator
|
|
597
|
-
4. The orchestrator dispatches the real execution job with the resolved values
|
|
598
|
-
|
|
599
|
-
This adds approximately 5-10 seconds of overhead for cloning and evaluation.
|
|
600
|
-
|
|
601
|
-
**Examples of impure functions (will use init job):**
|
|
602
|
-
|
|
603
|
-
```typescript
|
|
604
|
-
// Async functions cannot be inlined
|
|
605
|
-
environment: async (event) => await lookupEnv(event.targetBranch);
|
|
606
|
-
|
|
607
|
-
// External module references
|
|
608
|
-
env: (event) => {
|
|
609
|
-
const config = require('./config');
|
|
610
|
-
return config.env;
|
|
611
|
-
};
|
|
612
|
-
|
|
613
|
-
// Process/global access
|
|
614
|
-
environment: (event) => process.env.DEFAULT_ENV || 'staging';
|
|
615
|
-
|
|
616
|
-
// Dynamic imports
|
|
617
|
-
env: async (event) => {
|
|
618
|
-
const m = await import('./config.js');
|
|
619
|
-
return m.default;
|
|
620
|
-
};
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
## Performance comparison
|
|
624
|
-
|
|
625
|
-
| Evaluation path | Overhead | When used |
|
|
626
|
-
| ------------------------------------ | -------- | ------------------------------------------------------------------- |
|
|
627
|
-
| Static value (string/object literal) | ~0ms | `environment: 'staging'` |
|
|
628
|
-
| Inline expression (pure function) | ~0ms | `environment: (event) => event.targetBranch` |
|
|
629
|
-
| Init job (impure function) | ~5-10s | `environment: async (event) => await lookupEnv(event.targetBranch)` |
|
|
630
|
-
|
|
631
|
-
## Tips
|
|
632
|
-
|
|
633
|
-
- **Write pure functions whenever possible** to avoid the init-job delay. Most environment and env computations only need the event payload data.
|
|
634
|
-
- **Check compiler warnings** -- the compiler tells you when a function is classified as impure and explains why.
|
|
635
|
-
- **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.
|
|
636
|
-
- **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`, …).
|
|
637
|
-
|
|
638
|
-
---
|
|
639
|
-
|
|
640
|
-
## Environment variables
|
|
641
|
-
|
|
642
|
-
Source: https://docs.kici.dev/user/env-vars/
|
|
643
|
-
|
|
644
|
-
The KiCI CLI reads the following environment variables to customize its behavior. OAuth login (`kici login` without `--token`) defaults `KICI_PLATFORM_URL`, `KICI_OIDC_ISSUER`, and `KICI_OIDC_CLIENT_ID` to the hosted KiCI Platform, so `kici login` works with no configuration. Set them only to target another KiCI environment (e.g. a testing instance) or a custom OIDC provider.
|
|
645
|
-
|
|
646
|
-
## Authentication
|
|
647
|
-
|
|
648
|
-
| Variable | Description | Default |
|
|
649
|
-
| --------------------- | -------------------------------------- | -------------------------------------------- |
|
|
650
|
-
| `KICI_OIDC_ISSUER` | OIDC issuer URL for authentication | `https://auth.kici.dev/realms/kici-internal` |
|
|
651
|
-
| `KICI_OIDC_CLIENT_ID` | OIDC client ID for the CLI application | `kici-cli` |
|
|
652
|
-
| `KICI_PLATFORM_URL` | Platform API base URL | `https://api.kici.dev` |
|
|
653
|
-
| `KICI_CONFIG_DIR` | Override the KiCI config directory | `~/.kici` |
|
|
654
|
-
|
|
655
|
-
## Browser behavior
|
|
656
|
-
|
|
657
|
-
| Variable | Description | Default |
|
|
658
|
-
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
659
|
-
| `KICI_BROWSER_CMD` | Custom browser command for OAuth login. Supports `{url}` placeholder. Set to `none` to suppress browser opening and print the URL to stdout instead. | Uses the system default browser via the `open` package |
|
|
660
|
-
| `KICI_CALLBACK_PORT` | Fixed port for the OAuth PKCE callback server. Useful when firewall rules require a known port. | Random available port |
|
|
661
|
-
|
|
662
|
-
## Development
|
|
663
|
-
|
|
664
|
-
| Variable | Description | Default |
|
|
665
|
-
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
|
666
|
-
| `KICI_DEV` | Enable development mode. When `true`, uses prerelease-compatible version ranges (`>=0.0.1-0`) for dev dependencies and skips npm version resolution. | unset |
|
|
667
|
-
| `KICI_DEBUG` | Enable debug logging. When `true`, prints verbose diagnostics (SDK alias resolution, step-level debug logs, stack traces on errors). Equivalent to the `--debug` CLI flag. | unset |
|
|
668
|
-
|
|
669
|
-
## Usage examples
|
|
670
|
-
|
|
671
|
-
### CI/CD environment
|
|
672
|
-
|
|
673
|
-
Authenticate with a pre-existing API key (no browser needed):
|
|
674
|
-
|
|
675
|
-
```bash
|
|
676
|
-
kici login --token <<< "$KICI_API_KEY"
|
|
677
|
-
```
|
|
678
|
-
|
|
679
|
-
### Targeting another environment or custom OIDC provider
|
|
680
|
-
|
|
681
|
-
`kici login` targets the hosted KiCI Platform by default. To point the CLI at another KiCI environment (e.g. a testing instance) or a custom OIDC provider, override the defaults:
|
|
682
|
-
|
|
683
|
-
```bash
|
|
684
|
-
export KICI_OIDC_ISSUER=https://your-idp.example.com
|
|
685
|
-
export KICI_OIDC_CLIENT_ID=your-client-id
|
|
686
|
-
export KICI_PLATFORM_URL=https://your-platform.example.com
|
|
687
|
-
kici login
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
### Headless SSH session
|
|
691
|
-
|
|
692
|
-
The CLI auto-detects headless environments and uses the device flow. To force PKCE with URL output instead:
|
|
693
|
-
|
|
694
|
-
```bash
|
|
695
|
-
export KICI_BROWSER_CMD=none
|
|
696
|
-
kici login
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
This prints the authorization URL to stdout as `KICI_AUTH_URL=<url>`. Open the URL in any browser to complete authentication.
|
|
700
|
-
|
|
701
|
-
### Fixed callback port
|
|
702
|
-
|
|
703
|
-
When behind a firewall or using port forwarding:
|
|
704
|
-
|
|
705
|
-
```bash
|
|
706
|
-
export KICI_CALLBACK_PORT=19876
|
|
707
|
-
kici login
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
### Custom config location
|
|
711
|
-
|
|
712
|
-
Store the KiCI config in a non-default location:
|
|
713
|
-
|
|
714
|
-
```bash
|
|
715
|
-
export KICI_CONFIG_DIR=/tmp/kici-test
|
|
716
|
-
kici login
|
|
717
|
-
```
|
|
718
|
-
|
|
719
|
-
---
|
|
720
|
-
|
|
721
|
-
## Environments
|
|
489
|
+
## Contexts
|
|
722
490
|
|
|
723
|
-
Source: https://docs.kici.dev/user/
|
|
491
|
+
Source: https://docs.kici.dev/user/contexts/
|
|
724
492
|
|
|
725
|
-
<!-- help:
|
|
493
|
+
<!-- help:contexts-list#overview -->
|
|
726
494
|
|
|
727
|
-
|
|
495
|
+
Contexts are named deployment targets (like staging or production) that control where your workflow jobs run. Each context can have its own variables, secrets, and protection rules to gate deployments.
|
|
728
496
|
|
|
729
|
-
<!-- /help:
|
|
497
|
+
<!-- /help:contexts-list -->
|
|
730
498
|
|
|
731
|
-
<!-- help:
|
|
499
|
+
<!-- help:contexts-protection#protection-rules -->
|
|
732
500
|
|
|
733
|
-
Protection rules control when jobs targeting
|
|
501
|
+
Protection rules control when jobs targeting a context can execute.
|
|
734
502
|
|
|
735
503
|
Available rules:
|
|
736
504
|
|
|
@@ -739,24 +507,24 @@ Available rules:
|
|
|
739
507
|
- **Wait timers** — delay execution for a fixed period.
|
|
740
508
|
- **Concurrency limits** — prevent collisions between parallel deployments.
|
|
741
509
|
|
|
742
|
-
<!-- /help:
|
|
510
|
+
<!-- /help:contexts-protection -->
|
|
743
511
|
|
|
744
|
-
|
|
512
|
+
Contexts represent deployment targets like `staging`, `production`, or `review/PR-*`. Each context can have its own variables, bound secrets, and protection rules that control when and how jobs targeting that context can execute.
|
|
745
513
|
|
|
746
514
|
## Overview
|
|
747
515
|
|
|
748
|
-
|
|
516
|
+
A context in KiCI provides:
|
|
749
517
|
|
|
750
518
|
- **Variables** -- non-secret key-value configuration (e.g., `API_URL`, `CLUSTER_NAME`)
|
|
751
|
-
- **Scoped secrets** -- encrypted values bound to the
|
|
519
|
+
- **Scoped secrets** -- encrypted values bound to the context via scope bindings
|
|
752
520
|
- **Protection rules** -- branch restrictions, required reviewers, wait timers, and concurrency limits
|
|
753
521
|
- **Per-source overrides** -- repositories can override unlocked variables for their own deployments
|
|
754
522
|
|
|
755
523
|
## SDK API
|
|
756
524
|
|
|
757
|
-
### Job-level
|
|
525
|
+
### Job-level context property
|
|
758
526
|
|
|
759
|
-
The `
|
|
527
|
+
The `context` property is set on a job, not a workflow or step:
|
|
760
528
|
|
|
761
529
|
```typescript
|
|
762
530
|
import { workflow, job, step, push } from '@kici-dev/sdk';
|
|
@@ -766,16 +534,16 @@ export default workflow('deploy', {
|
|
|
766
534
|
jobs: [
|
|
767
535
|
job('deploy-staging', {
|
|
768
536
|
runsOn: 'default',
|
|
769
|
-
|
|
537
|
+
context: 'staging',
|
|
770
538
|
steps: [
|
|
771
539
|
step('deploy', async (ctx) => {
|
|
772
|
-
// ctx.
|
|
773
|
-
console.log(`Deploying to ${ctx.
|
|
774
|
-
// ctx.secrets provides async get/expose/has methods for
|
|
540
|
+
// ctx.context is the resolved context name
|
|
541
|
+
console.log(`Deploying to ${ctx.context}`);
|
|
542
|
+
// ctx.secrets provides async get/expose/has methods for context-bound secrets
|
|
775
543
|
const dbPassword = await ctx.secrets.get('DB_PASSWORD');
|
|
776
544
|
// Environment variables are in ctx.env
|
|
777
545
|
const apiUrl = ctx.env.API_URL;
|
|
778
|
-
await ctx.$`deploy --target ${ctx.
|
|
546
|
+
await ctx.$`deploy --target ${ctx.context}`;
|
|
779
547
|
}),
|
|
780
548
|
],
|
|
781
549
|
}),
|
|
@@ -783,54 +551,54 @@ export default workflow('deploy', {
|
|
|
783
551
|
});
|
|
784
552
|
```
|
|
785
553
|
|
|
786
|
-
### Dynamic
|
|
554
|
+
### Dynamic contexts
|
|
787
555
|
|
|
788
|
-
The
|
|
556
|
+
The context name can be a string or a function (sync or async) for dynamic contexts (e.g., per-PR review contexts). The function receives the normalized event envelope, with the raw provider body nested at `event.payload`:
|
|
789
557
|
|
|
790
558
|
```typescript
|
|
791
559
|
job('deploy-review', {
|
|
792
560
|
runsOn: 'default',
|
|
793
|
-
|
|
561
|
+
context: (event) => `review/PR-${event.payload.pull_request.number}`,
|
|
794
562
|
steps: [
|
|
795
563
|
step('deploy', async (ctx) => {
|
|
796
|
-
// ctx.
|
|
797
|
-
await ctx.$`deploy-preview --env ${ctx.
|
|
564
|
+
// ctx.context is 'review/PR-123' (resolved at runtime)
|
|
565
|
+
await ctx.$`deploy-preview --env ${ctx.context}`;
|
|
798
566
|
}),
|
|
799
567
|
],
|
|
800
568
|
});
|
|
801
569
|
```
|
|
802
570
|
|
|
803
|
-
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
|
|
571
|
+
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 contexts that match a glob pattern (e.g., `review/*`) inherit the pattern's configuration, variables, and protection rules.
|
|
804
572
|
|
|
805
|
-
### Multiple
|
|
573
|
+
### Multiple contexts per job
|
|
806
574
|
|
|
807
|
-
A job can bind more than one
|
|
575
|
+
A job can bind more than one context with `contexts`, an ordered array. This lets a single job draw secrets and variables from several contexts at once — for example a shared `staging` context plus a `my-testing` context that carries test-only variables:
|
|
808
576
|
|
|
809
577
|
```typescript
|
|
810
578
|
job('deploy', {
|
|
811
579
|
runsOn: 'default',
|
|
812
|
-
|
|
580
|
+
contexts: ['staging', 'my-testing'],
|
|
813
581
|
steps: [
|
|
814
582
|
step('deploy', async (ctx) => {
|
|
815
|
-
// ctx.secrets and ctx.env carry the merged set from both
|
|
583
|
+
// ctx.secrets and ctx.env carry the merged set from both contexts
|
|
816
584
|
const dbUrl = await ctx.secrets.get('DB_URL');
|
|
817
585
|
}),
|
|
818
586
|
],
|
|
819
587
|
});
|
|
820
588
|
```
|
|
821
589
|
|
|
822
|
-
- `
|
|
823
|
-
- Each array entry is a static name or a function of the event, resolved per element exactly like a single dynamic
|
|
590
|
+
- `context` (singular) and `contexts` (array) are mutually exclusive — setting both is a compile error. `context: 'staging'` is exactly equivalent to `contexts: ['staging']`.
|
|
591
|
+
- Each array entry is a static name or a function of the event, resolved per element exactly like a single dynamic context.
|
|
824
592
|
|
|
825
|
-
**Merge order — last wins.** All bound
|
|
593
|
+
**Merge order — last wins.** All bound contexts are resolved on every dispatch (webhook, scheduled, and test runs alike) and merged in array order. When the same secret or variable key is defined in more than one context, the later entry in the array wins. With `contexts: ['staging', 'my-testing']`, a key defined in both resolves to `my-testing`'s value; keys defined in only one are preserved. The longest-scope-path-wins rule still applies _within_ each context.
|
|
826
594
|
|
|
827
|
-
**Protection rules combine all-must-pass.** A job must satisfy **every** bound
|
|
595
|
+
**Protection rules combine all-must-pass.** A job must satisfy **every** bound context's gates — adding a context can never loosen access. Branch restrictions, trigger-type filters, and repo patterns must pass for all contexts; the minimum trust tier is the most restrictive across them; required reviewers are the union of all contexts' reviewers; the wait timer is the longest; and the hold expiry is the shortest. If a run is gated out, the rejection names which context and which rule rejected it (visible via `kici runs show <run-id>` and the run's rejection reason), so a mutually-exclusive set of rules surfaces as a clear failure rather than a silent perpetual rejection.
|
|
828
596
|
|
|
829
|
-
**Skip-on-test (allow-and-warn).** On a test or local run (`kici run remote`, `kici run local`), a bound
|
|
597
|
+
**Skip-on-test (allow-and-warn).** On a test or local run (`kici run remote`, `kici run <event> --local`), a bound context never rejects the run. Any bound context that disallows local execution (`allowLocalExecution: false`) — or that is not configured — is **skipped**: its variables and secrets are omitted from the merge and its gates are not evaluated. The run proceeds, and a user-visible warning naming the skipped context(s) is shown both on the `kici run remote` CLI output and on the dashboard run view. This makes the test-only-variables pattern work: with `contexts: ['staging', 'my-testing']` where only `my-testing` allows local execution, a test run resolves just `my-testing`'s variables and warns that `staging` was skipped. If every bound context is skipped, the job runs with no environment variables. This is intentionally different from a fixture `secrets:` mapping, which is fail-closed — see the [testing guide](https://docs.kici.dev/user/testing-guide/).
|
|
830
598
|
|
|
831
|
-
**Unconfigured
|
|
599
|
+
**Unconfigured contexts contribute nothing at dispatch.** At dispatch time a bound context name with no matching configured context (and no matching glob context) simply adds no variables, secrets, or protection rules — the job still runs, exactly as a single dynamic context resolving to an as-yet-unconfigured name does today.
|
|
832
600
|
|
|
833
|
-
**Registration rejects a provably-unsatisfiable binding.** When a workflow is registered, KiCI statically checks every multi-
|
|
601
|
+
**Registration rejects a provably-unsatisfiable binding.** When a workflow is registered, KiCI statically checks every multi-context binding: a bound context that does not exist, a disabled one, or two contexts with mutually-exclusive fixed branch / trigger-type / repository restrictions (no value can satisfy both) makes the binding provably unsatisfiable, and the registration is rejected with a precise message naming the job, the contexts, and the rule — for example `unsatisfiable context binding: job 'deploy' binds contexts [staging, my-testing] with mutually exclusive branch restrictions (no value satisfies all bound contexts)`. Bindings whose restrictions use globs are undecidable at registration and fall through to the dispatch-time gate check instead.
|
|
834
602
|
|
|
835
603
|
### Job-level environment variables
|
|
836
604
|
|
|
@@ -839,7 +607,7 @@ The `env` property on a job provides static or dynamic environment variables:
|
|
|
839
607
|
```typescript
|
|
840
608
|
job('deploy', {
|
|
841
609
|
runsOn: 'default',
|
|
842
|
-
|
|
610
|
+
context: 'production',
|
|
843
611
|
env: { DEPLOY_TARGET: 'us-east-1' },
|
|
844
612
|
// Or dynamic:
|
|
845
613
|
// env: (event) => ({ DEPLOY_SHA: event.payload.after?.slice(0, 7) }),
|
|
@@ -854,34 +622,32 @@ job('deploy', {
|
|
|
854
622
|
|
|
855
623
|
### Concurrency groups
|
|
856
624
|
|
|
857
|
-
Jobs can define their own concurrency groups to control concurrent execution within
|
|
625
|
+
Jobs can define their own concurrency groups to control concurrent execution within a context. For workflow-level concurrency (which applies to all jobs in a workflow), see [Concurrency groups](https://docs.kici.dev/user/concurrency/).
|
|
858
626
|
|
|
859
|
-
Control concurrent deployments to the same
|
|
627
|
+
Control concurrent deployments to the same context:
|
|
860
628
|
|
|
861
629
|
```typescript
|
|
862
630
|
job('deploy', {
|
|
863
631
|
runsOn: 'default',
|
|
864
|
-
|
|
632
|
+
context: 'production',
|
|
865
633
|
concurrencyGroup: 'production-api',
|
|
866
634
|
// Or dynamic:
|
|
867
635
|
// concurrencyGroup: (event) => `review-${event.payload.pull_request.number}`,
|
|
868
|
-
steps: [
|
|
869
|
-
/* ... */
|
|
870
|
-
],
|
|
636
|
+
steps: [/* ... */],
|
|
871
637
|
});
|
|
872
638
|
```
|
|
873
639
|
|
|
874
|
-
If no `concurrencyGroup` is specified, the
|
|
640
|
+
If no `concurrencyGroup` is specified, the context name is used as the default concurrency group. For a job bound to multiple contexts, the default is the **first** bound context's name.
|
|
875
641
|
|
|
876
642
|
### Step context
|
|
877
643
|
|
|
878
644
|
Inside a step, the `ctx` object provides:
|
|
879
645
|
|
|
880
|
-
| Property
|
|
881
|
-
|
|
|
882
|
-
| `ctx.
|
|
883
|
-
| `ctx.env`
|
|
884
|
-
| `ctx.secrets`
|
|
646
|
+
| Property | Type | Description |
|
|
647
|
+
| ------------- | ------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
648
|
+
| `ctx.context` | `string \| undefined` | Resolved context name (undefined for jobs without context) |
|
|
649
|
+
| `ctx.env` | `Record<string, string \| undefined>` | Environment variables (merged from system, org, source, and job-level `env`) |
|
|
650
|
+
| `ctx.secrets` | `StepSecretsTyped` | Async accessor for bound secrets (get, expose, has, getMeta, list, mountFile, exposeFile) |
|
|
885
651
|
|
|
886
652
|
| Method | Returns | Description |
|
|
887
653
|
| -------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -898,25 +664,25 @@ The full secrets API — including `SecretFileOptions`, log masking, and the can
|
|
|
898
664
|
|
|
899
665
|
## Environment variable merge precedence
|
|
900
666
|
|
|
901
|
-
When a job targets
|
|
667
|
+
When a job targets a context, variables are merged in this order (last wins):
|
|
902
668
|
|
|
903
669
|
1. **Allowed system vars** -- `PATH`, `HOME`, etc. from the agent process
|
|
904
670
|
2. **Sandbox defaults** -- `FORCE_COLOR=1`
|
|
905
671
|
3. **KICI\_\* system vars** -- orchestrator-generated metadata
|
|
906
|
-
4. **Org-level
|
|
672
|
+
4. **Org-level context vars** -- from the dashboard, managed per-context
|
|
907
673
|
5. **Source-level overrides** -- per-repository overrides (skips locked vars)
|
|
908
674
|
6. **Job env** -- from the `env` property in the SDK
|
|
909
675
|
7. **`setEnv()` calls** -- runtime modifications within steps
|
|
910
676
|
|
|
911
|
-
> **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/
|
|
677
|
+
> **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/contexts/#step-context) section above.
|
|
912
678
|
|
|
913
679
|
## Protection rules
|
|
914
680
|
|
|
915
|
-
|
|
681
|
+
Contexts can have protection rules that gate job execution:
|
|
916
682
|
|
|
917
683
|
### Branch restrictions
|
|
918
684
|
|
|
919
|
-
Limit which branches can deploy to
|
|
685
|
+
Limit which branches can deploy to a context:
|
|
920
686
|
|
|
921
687
|
```
|
|
922
688
|
Allowed branches: main, release/*
|
|
@@ -932,7 +698,7 @@ Require manual approval before a job can proceed:
|
|
|
932
698
|
Required reviewers: alice, bob
|
|
933
699
|
```
|
|
934
700
|
|
|
935
|
-
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-
|
|
701
|
+
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/runs-and-approvals/#kici-approve) command, or the API. Held runs expire after a configurable timeout.
|
|
936
702
|
|
|
937
703
|
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.
|
|
938
704
|
|
|
@@ -973,68 +739,72 @@ See the [CI security architecture docs](https://docs.kici.dev/architecture/secur
|
|
|
973
739
|
|
|
974
740
|
### Security approval queue
|
|
975
741
|
|
|
976
|
-
When a PR is held for security review (
|
|
742
|
+
When a PR is held for security review — the org trust policy held it (a non-trusted contributor modified `.kici/` files, the PR came from a fork, or the contributor could not be resolved to a known identity), or a `minimumTrust` gate blocked the contributor — it enters the security approval queue. This is separate from the context approval queue: a security hold asks "is it safe to run this contributor's code at all?", while a context approval hold asks "should this job be promoted?". The two never cross — releasing a security hold needs `ci_trust:write` or higher, releasing a context approval hold needs `contexts:write` plus eligibility for one of the gate's clauses. See [Approval holds vs security holds](https://docs.kici.dev/architecture/approvals/#approval-holds-vs-security-holds) for the full comparison.
|
|
977
743
|
|
|
978
744
|
Held runs can be approved:
|
|
979
745
|
|
|
980
746
|
- Via the **dashboard** in Settings > CI trust > Approval queue
|
|
981
747
|
- Via a PR comment: `/kici approve` (commenter must have `ci_trust:write+`)
|
|
982
748
|
|
|
983
|
-
|
|
749
|
+
A hold raised by the org trust policy covers the whole PR and uses the org's approval expiry (default 72 hours). A `minimumTrust` hold is raised by a context rather than by the org policy, so it uses that context's own hold expiry (default one hour).
|
|
750
|
+
|
|
751
|
+
While the org trust policy is holding a pull request, your organization's global workflows do not run for it. Approving the hold releases that pull request's own workflows; it does not retroactively run the organization's global workflows for the event.
|
|
984
752
|
|
|
985
753
|
### Concurrency limits
|
|
986
754
|
|
|
987
|
-
Control how many jobs can run simultaneously in
|
|
755
|
+
Control how many jobs can run simultaneously in a context:
|
|
988
756
|
|
|
989
757
|
```
|
|
990
758
|
Concurrency limit: 1
|
|
991
759
|
Strategy: queue (or cancel-pending)
|
|
992
760
|
```
|
|
993
761
|
|
|
762
|
+
The concurrency limit is a positive integer; leave it unset for unlimited concurrency.
|
|
763
|
+
|
|
994
764
|
- **queue** -- new jobs wait in a FIFO queue (with configurable timeout, default 1 hour)
|
|
995
765
|
- **cancel-pending** -- pending (queued) jobs are cancelled when the limit is reached
|
|
996
766
|
|
|
997
767
|
## Dashboard management
|
|
998
768
|
|
|
999
|
-
### Creating
|
|
769
|
+
### Creating contexts
|
|
1000
770
|
|
|
1001
|
-
Navigate to **Settings >
|
|
771
|
+
Navigate to **Settings > Contexts** in the dashboard. Click **New context** to choose the context name and type (Fixed or Glob).
|
|
1002
772
|
|
|
1003
|
-
- **Fixed** -- applies to jobs that declare exactly this
|
|
1004
|
-
- **Glob** -- applies to any
|
|
773
|
+
- **Fixed** -- applies to jobs that declare exactly this context name, like `staging` or `production`
|
|
774
|
+
- **Glob** -- applies to any context name a job declares that matches the pattern, e.g. `review/*` matches a job with `context: 'review/PR-123'`
|
|
1005
775
|
|
|
1006
|
-
The
|
|
776
|
+
The contexts list shows each context'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.
|
|
1007
777
|
|
|
1008
|
-
###
|
|
778
|
+
### Context detail page
|
|
1009
779
|
|
|
1010
|
-
Each
|
|
780
|
+
Each context has four tabs:
|
|
1011
781
|
|
|
1012
782
|
1. **Variables** -- manage key-value pairs with lock toggles. Locked variables cannot be overridden by source-level overrides. Source overrides are managed in a sub-tab.
|
|
1013
783
|
|
|
1014
784
|
2. **Secrets** -- view bound secret scopes and their resolved secret count. Add bindings by specifying scope glob patterns (e.g., `aws/prod/**`).
|
|
1015
785
|
|
|
1016
|
-
3. **Protection** -- configure branch restrictions, required reviewers, wait timers, and concurrency limits with enable toggles for each section.
|
|
786
|
+
3. **Protection** -- configure branch restrictions, required reviewers, wait timers, and concurrency limits with enable toggles for each section. Turning a section's toggle off and saving clears that rule on the context, so the gate stops applying to new runs. Emptying the hold expiry field clears it too, and held runs fall back to the default one-hour hold window.
|
|
1017
787
|
|
|
1018
|
-
4. **History** -- view filtered runs targeting this
|
|
788
|
+
4. **History** -- view filtered runs targeting this context.
|
|
1019
789
|
|
|
1020
|
-
### Bound
|
|
790
|
+
### Bound contexts on runs
|
|
1021
791
|
|
|
1022
|
-
A job's bound deployment
|
|
792
|
+
A job's bound deployment contexts are shown as chips on the run detail page (in the job metadata panel) in the order the job declared them, and the distinct set across a run's jobs appears as compact chips on the run list. For a multi-context job the chips read left-to-right in merge order — later contexts override earlier ones on key collisions. A `(dynamic)` chip marks a context whose name is computed at runtime; it resolves to the real name once the run starts. A job that binds a single context shows one chip; a job that binds none shows no chip.
|
|
1023
793
|
|
|
1024
|
-
If a multi-
|
|
794
|
+
If a multi-context binding is gated out, the run's failure banner names which context and which rule rejected it (the same all-must-pass detail surfaced by `kici runs show <run-id>`).
|
|
1025
795
|
|
|
1026
796
|
### Secrets management
|
|
1027
797
|
|
|
1028
|
-
Secrets are individual encrypted values organized by scope paths (e.g., `aws/prod`, `databases/postgres`). Scopes are bound to
|
|
798
|
+
Secrets are individual encrypted values organized by scope paths (e.g., `aws/prod`, `databases/postgres`). Scopes are bound to contexts via bindings:
|
|
1029
799
|
|
|
1030
|
-
- **Scope-centric view** (Secrets page): tree view of scopes with per-scope
|
|
1031
|
-
- **
|
|
800
|
+
- **Scope-centric view** (Secrets page): tree view of scopes with per-scope context binding checkboxes
|
|
801
|
+
- **Context-centric view** (inside context detail): bound scopes, resolved secrets, add binding
|
|
1032
802
|
|
|
1033
803
|
When scope paths collide on the same key name, the longer (more specific) path wins.
|
|
1034
804
|
|
|
1035
805
|
## Type generation
|
|
1036
806
|
|
|
1037
|
-
Running `kici types` generates two augmented interfaces: `KnownSecretKeys` (union of all secret keys across all
|
|
807
|
+
Running `kici types` generates two augmented interfaces: `KnownSecretKeys` (union of all secret keys across all contexts) and `ContextSecrets` (per-context key unions):
|
|
1038
808
|
|
|
1039
809
|
```typescript
|
|
1040
810
|
interface KnownSecretKeys {
|
|
@@ -1042,13 +812,262 @@ interface KnownSecretKeys {
|
|
|
1042
812
|
API_KEY: string;
|
|
1043
813
|
}
|
|
1044
814
|
|
|
1045
|
-
interface
|
|
815
|
+
interface ContextSecrets {
|
|
1046
816
|
production: 'DB_PASSWORD' | 'API_KEY';
|
|
1047
817
|
staging: 'DB_PASSWORD';
|
|
1048
818
|
}
|
|
1049
819
|
```
|
|
1050
820
|
|
|
1051
|
-
`KnownSecretKeys` narrows `ctx.secrets.get()` and `ctx.secrets.expose()` key parameters to valid key names. `
|
|
821
|
+
`KnownSecretKeys` narrows `ctx.secrets.get()` and `ctx.secrets.expose()` key parameters to valid key names. `ContextSecrets` maps each context to its available secret key names as a string union. Dynamic contexts fall back to the full `KnownSecretKeys` union.
|
|
822
|
+
|
|
823
|
+
---
|
|
824
|
+
|
|
825
|
+
## Dashboard
|
|
826
|
+
|
|
827
|
+
Source: https://docs.kici.dev/user/dashboard/
|
|
828
|
+
|
|
829
|
+
The KiCI dashboard is the browser interface for monitoring workflow runs, inspecting jobs and logs, and managing your organization. It signs in via OIDC and talks to KiCI over its API.
|
|
830
|
+
|
|
831
|
+
This guide is split across the following pages:
|
|
832
|
+
|
|
833
|
+
| Page | Covers |
|
|
834
|
+
| ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
835
|
+
| [Getting started](https://docs.kici.dev/user/dashboard/getting-started/) | Onboarding checklist and your organizations list |
|
|
836
|
+
| [Navigation and layout](https://docs.kici.dev/user/dashboard/navigation/) | Sidebar, mobile nav, theme, time display, shortcuts, error pages |
|
|
837
|
+
| [Runs and logs](https://docs.kici.dev/user/dashboard/runs/) | Run list, run detail, the log viewer |
|
|
838
|
+
| [Settings](https://docs.kici.dev/user/dashboard/settings/) | Members, roles, teams, keys, sources, billing, security, support access |
|
|
839
|
+
| [Workflows, diagnostics, and orchestrators](https://docs.kici.dev/user/dashboard/workflows-and-diagnostics/) | Registered workflows, infra health, per-cluster views |
|
|
840
|
+
| [Contexts, secrets, and approvals](https://docs.kici.dev/user/dashboard/contexts-and-secrets/) | Contexts, secret scopes, approval queue |
|
|
841
|
+
| [Activity and DLQ](https://docs.kici.dev/user/dashboard/activity-and-dlq/) | Forensic activity log and dead-letter queue |
|
|
842
|
+
| [Notifications](https://docs.kici.dev/user/dashboard/notifications/) | Personal run notifications and org-wide channels, subscriptions, delivery log |
|
|
843
|
+
| [Account](https://docs.kici.dev/user/dashboard/account/) | Personal account settings |
|
|
844
|
+
|
|
845
|
+
---
|
|
846
|
+
|
|
847
|
+
## Dynamic values
|
|
848
|
+
|
|
849
|
+
Source: https://docs.kici.dev/user/dynamic-values/
|
|
850
|
+
|
|
851
|
+
Dynamic values let you compute `context`, `env`, and `concurrencyGroup` at runtime based on the incoming event. Instead of hardcoding static strings, you pass a function that receives the normalized event envelope and returns the resolved value.
|
|
852
|
+
|
|
853
|
+
```typescript
|
|
854
|
+
job('deploy', {
|
|
855
|
+
runsOn: ['default'],
|
|
856
|
+
context: (event) => event.targetBranch,
|
|
857
|
+
env: (event) => ({ BRANCH: event.targetBranch }),
|
|
858
|
+
concurrencyGroup: (event) => `deploy-${event.targetBranch}`,
|
|
859
|
+
steps: [/* ... */],
|
|
860
|
+
});
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
```typescript
|
|
864
|
+
job('deploy', {
|
|
865
|
+
runsOn: 'default',
|
|
866
|
+
// One shape everywhere: branch on the normalized event type.
|
|
867
|
+
context: (event) => (event.type === 'pull_request' ? 'preview' : 'production'),
|
|
868
|
+
steps: [/* ... */],
|
|
869
|
+
});
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
## How it works
|
|
873
|
+
|
|
874
|
+
When you define a dynamic value as a function, the compiler analyzes it at compile time to determine whether it is **pure** (can be evaluated without cloning the repo or running an init job).
|
|
875
|
+
|
|
876
|
+
### Pure functions (inline evaluation)
|
|
877
|
+
|
|
878
|
+
A pure function is one that:
|
|
879
|
+
|
|
880
|
+
- Is synchronous (no `async`/`await`)
|
|
881
|
+
- Only references its parameters and local variables
|
|
882
|
+
- Does not import or require external modules
|
|
883
|
+
- Does not access globals like `process`, `fetch`, `console`, `setTimeout`, etc.
|
|
884
|
+
- Uses only safe built-in constructors: `String`, `Number`, `Boolean`, `Array`, `Object`, `JSON`, `Math`, `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `encodeURIComponent`, `decodeURIComponent`, `encodeURI`, `decodeURI`
|
|
885
|
+
- Does not use `this`, `new`, `class`, `throw`, `try`/`catch`, `delete`, `var`, `yield`, or mutation operators (`++`, `--`, `+=`, etc.)
|
|
886
|
+
|
|
887
|
+
When the compiler detects a pure function, it serializes the function source directly into the lock file as an inline expression. At dispatch time, the orchestrator evaluates the expression in a sandboxed VM context (~0ms overhead) instead of dispatching an init job.
|
|
888
|
+
|
|
889
|
+
**Examples of pure functions:**
|
|
890
|
+
|
|
891
|
+
```typescript
|
|
892
|
+
// Simple branch extraction
|
|
893
|
+
context: (event) => event.targetBranch;
|
|
894
|
+
|
|
895
|
+
// Object literal with string operations
|
|
896
|
+
env: (event) => ({ BRANCH: event.targetBranch });
|
|
897
|
+
|
|
898
|
+
// Concatenation with event data
|
|
899
|
+
concurrencyGroup: (event) => `deploy-${event.targetBranch}`;
|
|
900
|
+
|
|
901
|
+
// Using safe globals
|
|
902
|
+
env: (event) => ({ UPPER: String(event.targetBranch).toUpperCase() });
|
|
903
|
+
|
|
904
|
+
// Local variables are fine
|
|
905
|
+
context: (event) => {
|
|
906
|
+
const parts = event.targetBranch.split('/');
|
|
907
|
+
return parts[parts.length - 1];
|
|
908
|
+
};
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
### Impure functions (init-job evaluation)
|
|
912
|
+
|
|
913
|
+
If the compiler determines a function is impure, it prints a `warning [W101]` naming the affected field (`context`, `env`, or `concurrencyGroup`), the reason the function was judged impure, and the ~5-10 second init-job cost — and compilation still succeeds. The function falls back to the two-phase init model. This means:
|
|
914
|
+
|
|
915
|
+
1. The orchestrator dispatches a special `__init__` job to a builder agent
|
|
916
|
+
2. The builder agent clones the repository and evaluates the function
|
|
917
|
+
3. The resolved values are sent back to the orchestrator
|
|
918
|
+
4. The orchestrator dispatches the real execution job with the resolved values
|
|
919
|
+
|
|
920
|
+
This adds approximately 5-10 seconds of overhead for cloning and evaluation.
|
|
921
|
+
|
|
922
|
+
`kici preview` lists the injected `__init__` job under each affected job, so you can spot the init-job cost before the first run.
|
|
923
|
+
|
|
924
|
+
**Examples of impure functions (will use init job):**
|
|
925
|
+
|
|
926
|
+
```typescript
|
|
927
|
+
// Async functions cannot be inlined
|
|
928
|
+
context: async (event) => await lookupEnv(event.targetBranch);
|
|
929
|
+
|
|
930
|
+
// External module references
|
|
931
|
+
env: (event) => {
|
|
932
|
+
const config = require('./config');
|
|
933
|
+
return config.env;
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
// Process/global access
|
|
937
|
+
context: (event) => process.env.DEFAULT_ENV || 'staging';
|
|
938
|
+
|
|
939
|
+
// Dynamic imports
|
|
940
|
+
env: async (event) => {
|
|
941
|
+
const m = await import('./config.js');
|
|
942
|
+
return m.default;
|
|
943
|
+
};
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
## Performance comparison
|
|
947
|
+
|
|
948
|
+
| Evaluation path | Overhead | When used |
|
|
949
|
+
| ------------------------------------ | -------- | --------------------------------------------------------------- |
|
|
950
|
+
| Static value (string/object literal) | ~0ms | `context: 'staging'` |
|
|
951
|
+
| Inline expression (pure function) | ~0ms | `context: (event) => event.targetBranch` |
|
|
952
|
+
| Init job (impure function) | ~5-10s | `context: async (event) => await lookupEnv(event.targetBranch)` |
|
|
953
|
+
|
|
954
|
+
## Tips
|
|
955
|
+
|
|
956
|
+
- **Write pure functions whenever possible** to avoid the init-job delay. Most context and env computations only need the event payload data.
|
|
957
|
+
- **Check compiler warnings** -- the compiler prints a `warning [W101]` when a function is classified as impure, naming the reason and the ~5-10s init-job cost. Run `kici preview` to see the injected `__init__` job listed under each affected job before your first run.
|
|
958
|
+
- **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.
|
|
959
|
+
- **See [how your workflow code executes](https://docs.kici.dev/user/execution-model/)** for the full picture of where pure vs. impure functions run relative to rules, hooks, and step bodies.
|
|
960
|
+
- **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`, …).
|
|
961
|
+
|
|
962
|
+
---
|
|
963
|
+
|
|
964
|
+
## Environment variables
|
|
965
|
+
|
|
966
|
+
Source: https://docs.kici.dev/user/env-vars/
|
|
967
|
+
|
|
968
|
+
The KiCI CLI reads the following environment variables to customize its behavior. OAuth login (`kici login` without `--token`) defaults `KICI_PLATFORM_URL`, `KICI_OIDC_ISSUER`, and `KICI_OIDC_CLIENT_ID` to the hosted KiCI Platform, so `kici login` works with no configuration. Set them only to target another KiCI environment (e.g. a testing instance) or a custom OIDC provider.
|
|
969
|
+
|
|
970
|
+
## Authentication
|
|
971
|
+
|
|
972
|
+
| Variable | Description | Default |
|
|
973
|
+
| --------------------- | -------------------------------------- | -------------------------------------------- |
|
|
974
|
+
| `KICI_OIDC_ISSUER` | OIDC issuer URL for authentication | `https://auth.kici.dev/realms/kici-internal` |
|
|
975
|
+
| `KICI_OIDC_CLIENT_ID` | OIDC client ID for the CLI application | `kici-cli` |
|
|
976
|
+
| `KICI_PLATFORM_URL` | Platform API base URL | `https://api.kici.dev` |
|
|
977
|
+
| `KICI_CONFIG_DIR` | Override the KiCI config directory | `~/.kici` |
|
|
978
|
+
|
|
979
|
+
## Browser behavior
|
|
980
|
+
|
|
981
|
+
| Variable | Description | Default |
|
|
982
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
|
|
983
|
+
| `KICI_BROWSER_CMD` | Custom browser command for OAuth login. Supports `{url}` placeholder. Set to `none` to suppress browser opening and print the URL to stdout instead. | Uses the system default browser |
|
|
984
|
+
| `KICI_CALLBACK_PORT` | Fixed port for the OAuth PKCE callback server. Useful when firewall rules require a known port. | Random available port |
|
|
985
|
+
|
|
986
|
+
## Development
|
|
987
|
+
|
|
988
|
+
| Variable | Description | Default |
|
|
989
|
+
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
|
990
|
+
| `KICI_DEV` | Enable development mode. When `true`, uses prerelease-compatible version ranges (`>=0.0.1-0`) for dev dependencies and skips npm version resolution. | unset |
|
|
991
|
+
| `KICI_DEBUG` | Enable debug logging. When `true`, prints verbose diagnostics (SDK alias resolution, step-level debug logs, stack traces on errors). Equivalent to the `--debug` CLI flag. | unset |
|
|
992
|
+
|
|
993
|
+
## Local dev plane
|
|
994
|
+
|
|
995
|
+
Read by the [local dev plane](https://docs.kici.dev/user/cli/authoring-and-local/#kici-local) that `kici run <event> --local` dispatches through.
|
|
996
|
+
|
|
997
|
+
| Variable | Description | Default |
|
|
998
|
+
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
|
|
999
|
+
| `KICI_LOCAL_ORCH_PORT` | Port the plane orchestrator listens on (HTTP + WebSocket). Change it when another process already holds the default. | `4319` |
|
|
1000
|
+
| `KICI_LOCAL_PG_PORT` | Port the plane's PostgreSQL listens on. | `45432` |
|
|
1001
|
+
| `KICI_LOCAL_PG_MODE` | Set to `podman` to force the container PostgreSQL fallback instead of the embedded binary. | Embedded PostgreSQL |
|
|
1002
|
+
| `KICI_LOCAL_ACCEPTANCE_TIMEOUT_MS` | How long a local run waits for an agent to claim its first job before failing fast. Raise it on a slow host; the run still fails quickly when no scaler label set matches `runsOn`. | `120000` (2 minutes) |
|
|
1003
|
+
|
|
1004
|
+
## CI detection
|
|
1005
|
+
|
|
1006
|
+
The CLI also reads the conventional CI markers your CI provider sets. They are not KiCI variables — KiCI only consumes them.
|
|
1007
|
+
|
|
1008
|
+
| Variable | Description | Default |
|
|
1009
|
+
| ---------------- | -------------------------------------------- | ------- |
|
|
1010
|
+
| `CI` | Generic CI marker. Set by most CI providers. | unset |
|
|
1011
|
+
| `GITHUB_ACTIONS` | Set to `true` by GitHub Actions. | unset |
|
|
1012
|
+
| `GITLAB_CI` | Set to `true` by GitLab CI/CD. | unset |
|
|
1013
|
+
|
|
1014
|
+
### How `CI` is interpreted
|
|
1015
|
+
|
|
1016
|
+
`kici` treats the environment as CI when `CI`, `GITHUB_ACTIONS`, or `GITLAB_CI` is set to any value other than an explicit opt-out. `0` and `false` are the opt-outs, compared case-insensitively, so `CI=0`, `CI=false`, and `CI=False` all mean "not CI". Surrounding whitespace is ignored, and a value that is empty or only whitespace (`CI=`) is treated as unset.
|
|
1017
|
+
|
|
1018
|
+
A vendor marker outranks the generic opt-out: `CI=false GITHUB_ACTIONS=true` is still CI, because an explicit vendor marker names a real runner rather than a preference.
|
|
1019
|
+
|
|
1020
|
+
This affects which login flow `kici login` chooses (browser vs device) and whether interactive commands such as `kici init` prompt.
|
|
1021
|
+
|
|
1022
|
+
## Usage examples
|
|
1023
|
+
|
|
1024
|
+
### CI/CD environment
|
|
1025
|
+
|
|
1026
|
+
Authenticate with a pre-existing API key (no browser needed):
|
|
1027
|
+
|
|
1028
|
+
```bash
|
|
1029
|
+
kici login --token "$KICI_API_KEY"
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
### Targeting another environment or custom OIDC provider
|
|
1033
|
+
|
|
1034
|
+
`kici login` targets the hosted KiCI Platform by default. To point the CLI at another KiCI environment (e.g. a testing instance) or a custom OIDC provider, override the defaults:
|
|
1035
|
+
|
|
1036
|
+
```bash
|
|
1037
|
+
export KICI_OIDC_ISSUER=https://your-idp.example.com
|
|
1038
|
+
export KICI_OIDC_CLIENT_ID=your-client-id
|
|
1039
|
+
export KICI_PLATFORM_URL=https://your-platform.example.com
|
|
1040
|
+
kici login
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
### Headless SSH session
|
|
1044
|
+
|
|
1045
|
+
The CLI auto-detects headless environments and uses the device flow. To force PKCE with URL output instead:
|
|
1046
|
+
|
|
1047
|
+
```bash
|
|
1048
|
+
export KICI_BROWSER_CMD=none
|
|
1049
|
+
kici login
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
This prints the authorization URL to stdout as `KICI_AUTH_URL=<url>`. Open the URL in any browser to complete authentication.
|
|
1053
|
+
|
|
1054
|
+
### Fixed callback port
|
|
1055
|
+
|
|
1056
|
+
When behind a firewall or using port forwarding:
|
|
1057
|
+
|
|
1058
|
+
```bash
|
|
1059
|
+
export KICI_CALLBACK_PORT=19876
|
|
1060
|
+
kici login
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
### Custom config location
|
|
1064
|
+
|
|
1065
|
+
Store the KiCI config in a non-default location:
|
|
1066
|
+
|
|
1067
|
+
```bash
|
|
1068
|
+
export KICI_CONFIG_DIR=/tmp/kici-test
|
|
1069
|
+
kici login
|
|
1070
|
+
```
|
|
1052
1071
|
|
|
1053
1072
|
---
|
|
1054
1073
|
|
|
@@ -1383,11 +1402,11 @@ export const deployComplete = defineEvent(
|
|
|
1383
1402
|
);
|
|
1384
1403
|
```
|
|
1385
1404
|
|
|
1386
|
-
Then emit using the definition
|
|
1405
|
+
Then emit using the definition — the payload is checked against the schema:
|
|
1387
1406
|
|
|
1388
1407
|
```typescript
|
|
1389
1408
|
step('emit', async (ctx) => {
|
|
1390
|
-
await ctx.emit(deployComplete
|
|
1409
|
+
await ctx.emit(deployComplete, {
|
|
1391
1410
|
env: 'prod',
|
|
1392
1411
|
version: '1.2.3',
|
|
1393
1412
|
services: ['api', 'web'],
|
|
@@ -1442,7 +1461,7 @@ If you've only ever used per-repo workflows so far, start with the mental model
|
|
|
1442
1461
|
| Workflow repo | The repo whose `.kici/workflows/*.ts` file **declares** the global workflow. Holds the steps. Also known as the _authoring_ repo. |
|
|
1443
1462
|
| Source repo | The repo that **emits** the event (push / PR / tag / ...) that causes the global workflow to fire. The agent checks out this repo as the working copy. |
|
|
1444
1463
|
| Global | A workflow whose trigger carries one or more `repos:` glob patterns. The presence of `repos:` is what classifies a workflow as global. |
|
|
1445
|
-
| Authoring axis | Policy that answers "which repos may **author** global workflows?" Controlled by the allow-list in the dashboard's
|
|
1464
|
+
| Authoring axis | Policy that answers "which repos may **author** global workflows?" Controlled by the allow-list in the dashboard's _Allowed author repos_ setting. |
|
|
1446
1465
|
| Source axis | Policy that answers "which **source** repos' events are allowed to trigger global workflows?" Controlled by the deny-list in _Blocked source repos_. |
|
|
1447
1466
|
|
|
1448
1467
|
The two axes are independent. A global workflow fires only if it passes **both** — its authoring repo is allowed AND the source repo is not denied.
|
|
@@ -1497,7 +1516,7 @@ Global workflows are **opt-in per org**. In a fresh org, `repos:`-bearing workfl
|
|
|
1497
1516
|
|
|
1498
1517
|
| Setting | What it controls | Typical use |
|
|
1499
1518
|
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
1500
|
-
|
|
|
1519
|
+
| Allowed author repos | Restricts which repos can **author** (register) global workflows. Globs matched against the authoring repo identifier. When OFF, any repo in the org may author globals. | Lock authoring to `myorg/ci-*` so random product repos can't ship org-wide automation. |
|
|
1501
1520
|
| Blocked source repos | Blocks dispatch for events emitted from these **source** repos, regardless of authoring. Globs matched against the event source repo identifier. When OFF, events from any repo may trigger globals. | Protect against fork spam — e.g. `!myorg/*` via `myorg/fork-*`. |
|
|
1502
1521
|
| Elevated access | Authoring repos listed here get **read access to source-repo secrets** during execution. Globs matched against the authoring repo identifier. | A `myorg/ci-deploy` repo that needs to read a source repo's `NPM_TOKEN` to publish releases. |
|
|
1503
1522
|
|
|
@@ -1513,7 +1532,7 @@ The page is a two-state editor — changes are local until you click **Save chan
|
|
|
1513
1532
|
|
|
1514
1533
|
A global workflow fires only if:
|
|
1515
1534
|
|
|
1516
|
-
1. **The authoring repo is allowed.** If
|
|
1535
|
+
1. **The authoring repo is allowed.** If _Allowed author repos_ is ON, the workflow's authoring repo must match at least one allow-list glob. If OFF, any repo may author. Enforced at two points:
|
|
1517
1536
|
- At registration time (extraction from the lock file — non-matching globals are dropped with a warning).
|
|
1518
1537
|
- At dispatch time (defense-in-depth — policy changes after registration still take effect).
|
|
1519
1538
|
2. **The source repo is not denied.** If the event's source repo matches any glob in _Blocked source repos_, the global workflow is skipped. Enforced at dispatch time.
|
|
@@ -1615,7 +1634,7 @@ const configureNginx = step('configure-nginx', {
|
|
|
1615
1634
|
`summarize` is **required** whenever `check` is declared. `run` and `whenInSync`
|
|
1616
1635
|
both produce the same output type — one output shape per step, whichever path
|
|
1617
1636
|
runs. Every other step facet (`cache`, `rules`, `continueOnError`, `timeout`,
|
|
1618
|
-
`approval`, `onCancel`, `cleanup`, `outputs`) composes unchanged.
|
|
1637
|
+
`retry`, `approval`, `onCancel`, `cleanup`, `outputs`) composes unchanged.
|
|
1619
1638
|
|
|
1620
1639
|
A plain `step()` without `check` keeps its exact current behavior — the check
|
|
1621
1640
|
facet is fully optional.
|
|
@@ -1643,21 +1662,19 @@ guaranteed side-effect-free.
|
|
|
1643
1662
|
|
|
1644
1663
|
## Running in check mode
|
|
1645
1664
|
|
|
1646
|
-
`--check` and `--fail-on-drift`
|
|
1665
|
+
`--check` and `--fail-on-drift` control drift reporting on `kici run remote`:
|
|
1647
1666
|
|
|
1648
1667
|
```bash
|
|
1649
1668
|
# Apply (default): converge the workflow.
|
|
1650
|
-
kici run local
|
|
1669
|
+
kici run push --local
|
|
1651
1670
|
kici run remote my-fixture
|
|
1652
1671
|
|
|
1653
1672
|
# Check: report drift, change nothing. Always exits 0.
|
|
1654
|
-
kici run local push --check
|
|
1655
1673
|
kici run remote my-fixture --check
|
|
1656
1674
|
|
|
1657
|
-
# Check + fail on drift:
|
|
1658
|
-
#
|
|
1659
|
-
|
|
1660
|
-
kici run local push --check --fail-on-drift
|
|
1675
|
+
# Check + fail on drift: fail the run when any step reports drift. Use this as a
|
|
1676
|
+
# CI gate ("fail the build if prod has drifted").
|
|
1677
|
+
kici run remote my-fixture --check --fail-on-drift
|
|
1661
1678
|
```
|
|
1662
1679
|
|
|
1663
1680
|
`--fail-on-drift` only modifies check mode — passing it without `--check` is an
|
|
@@ -1696,7 +1713,7 @@ The two channels (Option A and Option C) compose. If you declare both, the agent
|
|
|
1696
1713
|
|
|
1697
1714
|
## Option A — `registries:` block
|
|
1698
1715
|
|
|
1699
|
-
Declare the registry in your workflow file and point its `tokenSecret` at a scoped secret using the qualified `<
|
|
1716
|
+
Declare the registry in your workflow file and point its `tokenSecret` at a scoped secret using the qualified `<context>:<secret-name>` syntax. The orchestrator resolves the token at dispatch time and the agent applies it for one `npm install` only.
|
|
1700
1717
|
|
|
1701
1718
|
```typescript
|
|
1702
1719
|
import { workflow, job, step, push } from '@kici-dev/sdk';
|
|
@@ -1713,7 +1730,7 @@ export default workflow('build', {
|
|
|
1713
1730
|
jobs: [
|
|
1714
1731
|
job('build', {
|
|
1715
1732
|
runsOn: 'default',
|
|
1716
|
-
|
|
1733
|
+
context: 'production',
|
|
1717
1734
|
steps: [
|
|
1718
1735
|
step('install-and-build', async (ctx) => {
|
|
1719
1736
|
// .kici/package.json can now reference @my-org/* packages
|
|
@@ -1729,7 +1746,7 @@ Per-field rules:
|
|
|
1729
1746
|
|
|
1730
1747
|
- **`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)).
|
|
1731
1748
|
- **`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`.
|
|
1732
|
-
- **`tokenSecret`** — Mandatory `<
|
|
1749
|
+
- **`tokenSecret`** — Mandatory `<context>:<secret-name>`. The orchestrator looks up the secret in the named context via the per-context secret resolver. The bare name **must not** contain a colon.
|
|
1733
1750
|
- **`alwaysAuth`** — Defaults to `true`. Forces npm to send the token on every request (even GETs), which is what most managed-registry providers require.
|
|
1734
1751
|
|
|
1735
1752
|
### How tokens reach `npm install`
|
|
@@ -1738,6 +1755,17 @@ The agent never writes the token bytes to your `.kici/.npmrc`. Each registry tok
|
|
|
1738
1755
|
|
|
1739
1756
|
After the install completes (success or failure), the agent restores the original `.kici/.npmrc` — your committed file is never permanently modified.
|
|
1740
1757
|
|
|
1758
|
+
### Package managers
|
|
1759
|
+
|
|
1760
|
+
The agent detects the package manager from the cloned repo — npm, pnpm, or yarn (classic and berry are both supported) — and applies the auth overlay the detected manager actually reads:
|
|
1761
|
+
|
|
1762
|
+
| Detected manager | Auth file the agent overlays | Notes |
|
|
1763
|
+
| ----------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1764
|
+
| npm, pnpm, yarn classic | `.kici/.npmrc` | Auth lines carry `${VAR}` references to the job-scoped token env vars. |
|
|
1765
|
+
| yarn berry (v2+) | `.kici/.yarnrc.yml` | Berry reads `.yarnrc.yml` instead of `.npmrc`, so the overlay uses berry's own registry/scope/auth keys with the same `${VAR}` indirection. |
|
|
1766
|
+
|
|
1767
|
+
Either file is restored on cleanup, exactly as described above.
|
|
1768
|
+
|
|
1741
1769
|
## Option C — committed `.kici/.npmrc` + `installEnv:`
|
|
1742
1770
|
|
|
1743
1771
|
If you'd rather hand-craft the `.npmrc`, commit it under `.kici/.npmrc` with `${VAR}` placeholders, then list each variable in the workflow's `installEnv:` block using the same qualified syntax as `tokenSecret`.
|
|
@@ -1759,14 +1787,14 @@ export default workflow('build', {
|
|
|
1759
1787
|
jobs: [
|
|
1760
1788
|
job('build', {
|
|
1761
1789
|
runsOn: 'default',
|
|
1762
|
-
|
|
1790
|
+
context: 'production',
|
|
1763
1791
|
steps: [step('build', async (ctx) => ctx.$`npm run build`)],
|
|
1764
1792
|
}),
|
|
1765
1793
|
],
|
|
1766
1794
|
});
|
|
1767
1795
|
```
|
|
1768
1796
|
|
|
1769
|
-
The orchestrator resolves `MY_NPM_TOKEN` from the `production`
|
|
1797
|
+
The orchestrator resolves `MY_NPM_TOKEN` from the `production` context's secret store and seeds it as `MY_NPM_TOKEN` (bare name) in the install subprocess. Your committed `.npmrc` reads it through `${MY_NPM_TOKEN}`.
|
|
1770
1798
|
|
|
1771
1799
|
This path is the right answer when:
|
|
1772
1800
|
|
|
@@ -1788,7 +1816,7 @@ export default workflow('build', {
|
|
|
1788
1816
|
jobs: [
|
|
1789
1817
|
job('mint-codeartifact-token', {
|
|
1790
1818
|
runsOn: 'default',
|
|
1791
|
-
|
|
1819
|
+
context: 'production',
|
|
1792
1820
|
steps: [
|
|
1793
1821
|
step('mint', async (ctx) => {
|
|
1794
1822
|
const awsKey = await ctx.secrets.get('AWS_ACCESS_KEY_ID');
|
|
@@ -1813,7 +1841,7 @@ export default workflow('build', {
|
|
|
1813
1841
|
}),
|
|
1814
1842
|
job('build', {
|
|
1815
1843
|
runsOn: 'default',
|
|
1816
|
-
|
|
1844
|
+
context: 'production',
|
|
1817
1845
|
needs: ['mint-codeartifact-token'],
|
|
1818
1846
|
steps: [step('build', async (ctx) => ctx.$`npm run build`)],
|
|
1819
1847
|
}),
|
|
@@ -1837,7 +1865,7 @@ registries: [
|
|
|
1837
1865
|
],
|
|
1838
1866
|
```
|
|
1839
1867
|
|
|
1840
|
-
Mint the token from a fine-grained PAT with `read:packages` scope, store it as a scoped secret in the `production`
|
|
1868
|
+
Mint the token from a fine-grained PAT with `read:packages` scope, store it as a scoped secret in the `production` context.
|
|
1841
1869
|
|
|
1842
1870
|
### GitLab Packages
|
|
1843
1871
|
|
|
@@ -1892,21 +1920,21 @@ registries: [
|
|
|
1892
1920
|
|
|
1893
1921
|
## Security model
|
|
1894
1922
|
|
|
1895
|
-
- **Per-
|
|
1896
|
-
- **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
|
|
1897
|
-
- **Lifecycle scripts disabled.** Whenever a private registry is in scope, the agent runs the install with `--ignore-scripts` (npm
|
|
1923
|
+
- **Per-context scoping.** Every `tokenSecret` and `installEnv` entry is qualified with a context name. The orchestrator runs the same protection-rule pipeline (branch / trust / concurrency / reviewer / wait-timer) against each named context **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 context **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.
|
|
1924
|
+
- **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 context lacks an explicit [minimum trust](https://docs.kici.dev/user/contexts/#minimum-trust) rule.
|
|
1925
|
+
- **Lifecycle scripts disabled.** Whenever a private registry is in scope, the agent runs the install with `--ignore-scripts` (npm, pnpm, and yarn classic alike; yarn berry gets the equivalent `enableScripts: false`). 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 or yarn 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.
|
|
1898
1926
|
- **Stderr is redacted.** If the install fails, the agent masks every token literal out of the surfaced stderr / stdout chunks before logging.
|
|
1899
1927
|
- **Job-scoped env-var names.** The synthesized auth env var is `KICI_NPM_TOKEN_<jobIdShort>_<i>` where `jobIdShort` is the first 8 chars of the dispatched job id. The name is unguessable from outside the install subprocess and not reused across jobs.
|
|
1900
1928
|
- **`.npmrc` restored.** Whatever the agent appended for one install is stripped (or the file unlinked) on cleanup, so the workspace is never permanently modified.
|
|
1901
1929
|
|
|
1902
1930
|
## Reviewer-gated installs
|
|
1903
1931
|
|
|
1904
|
-
When the named install
|
|
1932
|
+
When the named install context carries a protection rule that holds — a required reviewer (`hold`) or a wait timer (`wait`) — the install gate **pauses the whole workflow dispatch** instead of rejecting it. The run is created in the `held` state, no jobs are queued, and a workflow-scoped row appears on the held-runs page with a `Workflow` scope badge.
|
|
1905
1933
|
|
|
1906
1934
|
- **Reviewer hold:** the run waits for an approver. On approval the dispatch resumes from the install gate, resolves the token, and dispatches its jobs as a normal run. On rejection the run transitions to `cancelled` — no jobs ever run.
|
|
1907
1935
|
- **Wait timer:** the run waits out the timer and resumes automatically when it elapses.
|
|
1908
1936
|
|
|
1909
|
-
A `reject` protection outcome (for example a disabled
|
|
1937
|
+
A `reject` protection outcome (for example a disabled context or a branch the context forbids) still fails the dispatch loudly with a clear reason, exactly as before — the orchestrator never dispatches a run with an unresolved install token.
|
|
1910
1938
|
|
|
1911
1939
|
## Limitations
|
|
1912
1940
|
|
|
@@ -1917,19 +1945,19 @@ A `reject` protection outcome (for example a disabled environment or a branch th
|
|
|
1917
1945
|
|
|
1918
1946
|
The orchestrator exposes Prometheus counters and a histogram under the `kici_orch_install_secrets_*` prefix on its `/metrics` endpoint. They populate the **Install secrets resolution** Grafana dashboard and let operators graph install-secrets activity without digging through Loki.
|
|
1919
1947
|
|
|
1920
|
-
| Metric | Type | Labels | What it tells you
|
|
1921
|
-
| ------------------------------------------------------------- | --------- | ------------------------------ |
|
|
1922
|
-
| `kici_orch_install_secrets_decisions_total` | Counter | `decision`, `reason` | Pass / reject / hold volume. `decision=hold` (reason `held`) counts dispatches paused at a reviewer-gated install
|
|
1923
|
-
| `kici_orch_install_secrets_npm_registry_used_total` | Counter | `channel`, `provider`, `scope` | Per-channel + per-scope usage. `channel=registries` is Option A, `channel=install_env` is Option C. `scope=default` marks a no-scope default registry; `scope=-` marks Option C entries.
|
|
1924
|
-
| `kici_orch_install_secrets_contributor_stripped_total` | Counter | `trust_tier` | Number of dispatches where registry tokens were stripped because the contributor tier wasn't `trusted` (fork PRs from unknown / known contributors). Expected to be 0 in single-tenant orgs.
|
|
1925
|
-
| `kici_orch_install_secrets_token_resolution_duration_seconds` | Histogram | `environment` | Latency of per-environment secret resolution. Pathological tails (>500ms) usually mean a Vault timeout or a slow Postgres replica.
|
|
1948
|
+
| Metric | Type | Labels | What it tells you |
|
|
1949
|
+
| ------------------------------------------------------------- | --------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1950
|
+
| `kici_orch_install_secrets_decisions_total` | Counter | `decision`, `reason` | Pass / reject / hold volume. `decision=hold` (reason `held`) counts dispatches paused at a reviewer-gated install context. Reject reasons enumerate the failure mode: `malformed_ref`, `invalid_url_scheme`, `env_not_found`, `protection_rule_block`, `missing_token`, `missing_install_env`, etc. |
|
|
1951
|
+
| `kici_orch_install_secrets_npm_registry_used_total` | Counter | `channel`, `provider`, `scope` | Per-channel + per-scope usage. `channel=registries` is Option A, `channel=install_env` is Option C. `scope=default` marks a no-scope default registry; `scope=-` marks Option C entries. |
|
|
1952
|
+
| `kici_orch_install_secrets_contributor_stripped_total` | Counter | `trust_tier` | Number of dispatches where registry tokens were stripped because the contributor tier wasn't `trusted` (fork PRs from unknown / known contributors). Expected to be 0 in single-tenant orgs. |
|
|
1953
|
+
| `kici_orch_install_secrets_token_resolution_duration_seconds` | Histogram | `environment` | Latency of per-environment secret resolution. Pathological tails (>500ms) usually mean a Vault timeout or a slow Postgres replica. |
|
|
1926
1954
|
|
|
1927
1955
|
The dashboard JSON lives at `infra/terraform/modules/grafana/dashboards/install-secrets.json`; if you maintain your own monitoring stack, you can import it directly.
|
|
1928
1956
|
|
|
1929
1957
|
## See also
|
|
1930
1958
|
|
|
1931
|
-
- [Secrets](https://docs.kici.dev/user/secrets/) — how to seed the `<
|
|
1932
|
-
- [
|
|
1959
|
+
- [Secrets](https://docs.kici.dev/user/secrets/) — how to seed the `<context>:<secret-name>` values referenced by `tokenSecret` / `installEnv`.
|
|
1960
|
+
- [Contexts](https://docs.kici.dev/user/contexts/) — protection rules (`branch_restrictions`, `requires_review`, `minimum_trust`) that the install gate inherits.
|
|
1933
1961
|
- [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.
|
|
1934
1962
|
|
|
1935
1963
|
---
|
|
@@ -1958,13 +1986,20 @@ An attestation is a self-contained bundle holding three things:
|
|
|
1958
1986
|
commit, ref, workflow, run/job identifiers, timestamps).
|
|
1959
1987
|
- A **[DSSE](https://github.com/secure-systems-lab/dsse) signature** over that
|
|
1960
1988
|
statement, made with an ephemeral signing key generated for the run.
|
|
1961
|
-
- A short-lived **OIDC identity token** issued by
|
|
1962
|
-
the signature to the build identity. The token's identity claims
|
|
1963
|
-
(`repository`, `ref`, `sha`, run/job ids) are derived by the
|
|
1964
|
-
run itself — a step cannot forge them.
|
|
1989
|
+
- A short-lived **OIDC identity token** issued by your **orchestrator** that
|
|
1990
|
+
binds the signature to the build identity. The token's identity claims
|
|
1991
|
+
(`repository`, `ref`, `sha`, run/job ids) are derived by the orchestrator from
|
|
1992
|
+
the run itself — a step cannot forge them.
|
|
1993
|
+
|
|
1994
|
+
The orchestrator owns the provenance root of trust: it holds its own long-lived
|
|
1995
|
+
ES256 signing key, mints and signs the identity token **locally** from its own
|
|
1996
|
+
run records, and publishes its own OIDC discovery + public key set (JWKS). Builds
|
|
1997
|
+
therefore produce verifiable provenance with **no dependency on the hosted KiCI
|
|
1998
|
+
platform** — the availability, sovereignty, and air-gap story all follow from
|
|
1999
|
+
this.
|
|
1965
2000
|
|
|
1966
2001
|
Because the bundle carries the identity token and the public signing key, it is
|
|
1967
|
-
**offline-verifiable**: a verifier checks it against the
|
|
2002
|
+
**offline-verifiable**: a verifier checks it against the orchestrator's published
|
|
1968
2003
|
signing keys with no per-attestation online lookup.
|
|
1969
2004
|
|
|
1970
2005
|
## Attesting an artifact in a workflow
|
|
@@ -2013,8 +2048,11 @@ The identity token is fetched and masked in logs automatically — you never
|
|
|
2013
2048
|
handle it. The call returns `{ storageKey, subjectDigest, bundleMediaType }`
|
|
2014
2049
|
identifying the stored bundle.
|
|
2015
2050
|
|
|
2016
|
-
`ctx.attestProvenance` is only available inside a running job step
|
|
2017
|
-
|
|
2051
|
+
`ctx.attestProvenance` is only available inside a running job step; calling it
|
|
2052
|
+
outside one rejects with a clear error. `kici run --local` runs are supported:
|
|
2053
|
+
the offline local dev plane signs with a dev identity under the
|
|
2054
|
+
clearly-non-production issuer `kici-local`, and those bundles verify against a
|
|
2055
|
+
trust root exported with `kici local trust-root`.
|
|
2018
2056
|
|
|
2019
2057
|
### Requesting a raw identity token
|
|
2020
2058
|
|
|
@@ -2031,11 +2069,11 @@ step('mint', async (ctx) => {
|
|
|
2031
2069
|
|
|
2032
2070
|
The token is a short-lived (about 10 minutes) signed JWT scoped to the current
|
|
2033
2071
|
run and job. Its identity claims (`repository`, `ref`, `sha`, `kici_run_id`,
|
|
2034
|
-
`kici_job_id`) are derived by the
|
|
2035
|
-
spoof them. The returned token value is automatically masked in step logs,
|
|
2036
|
-
the step never holds
|
|
2037
|
-
|
|
2038
|
-
|
|
2072
|
+
`kici_job_id`) are derived by the orchestrator from the run context, so a step
|
|
2073
|
+
cannot spoof them. The returned token value is automatically masked in step logs,
|
|
2074
|
+
and the step never holds signing credentials — the orchestrator mints and signs
|
|
2075
|
+
the token on the step's behalf from its own run records. Like `attestProvenance`,
|
|
2076
|
+
it is only available inside a running job step.
|
|
2039
2077
|
|
|
2040
2078
|
## Verifying an attestation
|
|
2041
2079
|
|
|
@@ -2051,14 +2089,27 @@ kici verify-attestation [artifact] --bundle <path-or-url> [--trust-root <url-or-
|
|
|
2051
2089
|
|
|
2052
2090
|
### Which trust root do I use?
|
|
2053
2091
|
|
|
2054
|
-
The trust root is
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2092
|
+
The trust root is **your orchestrator's provenance issuer** — the orchestrator
|
|
2093
|
+
you `kici login` against, which owns the provenance signing key and publishes its
|
|
2094
|
+
own JWKS. That is the **default**: omit `--trust-root` and the verifier checks the
|
|
2095
|
+
bundle against your configured orchestrator automatically. There are three ways to
|
|
2096
|
+
verify, and offline is always the primary one:
|
|
2097
|
+
|
|
2098
|
+
1. **Offline against a JWKS / trust-root file (air-gap)** — export the
|
|
2099
|
+
`{ issuer, jwks }` file once with `kici-admin signing-key export --public` and
|
|
2100
|
+
verify against it with `--trust-root <file>`. No network needed at verify time.
|
|
2101
|
+
2. **Directly online against your orchestrator** — the default: the verifier
|
|
2102
|
+
resolves your orchestrator's discovery → JWKS. You can also POST a bundle to
|
|
2103
|
+
the orchestrator's native `POST /v1/verify-attestation` endpoint for a verdict
|
|
2104
|
+
against its live keys (fresh rotations / revocations included).
|
|
2105
|
+
3. **Against the hosted KiCI platform** — bundles produced before your
|
|
2106
|
+
orchestrator owned signing were signed by the hosted platform; those keep
|
|
2107
|
+
verifying forever. When no orchestrator is configured, the default falls back
|
|
2108
|
+
to the hosted platform's issuer so those historical bundles still verify with
|
|
2109
|
+
no flag.
|
|
2110
|
+
|
|
2111
|
+
You pass `--trust-root` to verify against a different environment or, most
|
|
2112
|
+
commonly, an offline `{ issuer, jwks }` file for air-gapped checks.
|
|
2062
2113
|
|
|
2063
2114
|
### Why you supply it out-of-band
|
|
2064
2115
|
|
|
@@ -2101,7 +2152,7 @@ when it does not (or on an error such as a missing flag or unreachable trust
|
|
|
2101
2152
|
root).
|
|
2102
2153
|
|
|
2103
2154
|
```bash
|
|
2104
|
-
# Default: verify against
|
|
2155
|
+
# Default: verify against your configured orchestrator (no --trust-root needed):
|
|
2105
2156
|
kici verify-attestation ./dist/app.tgz --bundle ./app.tgz.kici.json
|
|
2106
2157
|
|
|
2107
2158
|
# Override the trust root to verify against a specific issuer:
|
|
@@ -2115,7 +2166,7 @@ kici verify-attestation ./dist/app.tgz \
|
|
|
2115
2166
|
--trust-root ./kici-trust-root.json
|
|
2116
2167
|
```
|
|
2117
2168
|
|
|
2118
|
-
The full flag reference is in the [CLI reference](https://docs.kici.dev/user/cli-
|
|
2169
|
+
The full flag reference is in the [CLI reference](https://docs.kici.dev/user/cli/notifications-and-diagnostics/#kici-verify-attestation).
|
|
2119
2170
|
|
|
2120
2171
|
## Viewing attestations in the dashboard
|
|
2121
2172
|
|
|
@@ -2157,6 +2208,7 @@ The **Attestations** page lists every build-provenance attestation your organiza
|
|
|
2157
2208
|
- **Search** by artifact digest (exact `sha256:…`) or name to trace a specific artifact.
|
|
2158
2209
|
- **Filter** by verification status, repository, workflow, job, or date.
|
|
2159
2210
|
- Each row's **status badge** is the verdict KiCI recorded when the attestation was produced (`verified`, `failed`, `unverifiable`, or `pending`).
|
|
2211
|
+
- **Retry** on a `pending` row asks your orchestrator to mint that run's outstanding attestations now; **Retry pending** does the same for every pending run at once.
|
|
2160
2212
|
|
|
2161
2213
|
Open a row for the parsed provenance statement and a live re-verification.
|
|
2162
2214
|
|
|
@@ -2170,6 +2222,19 @@ the bundle did not pass; `unverifiable` means no verdict could be computed (no
|
|
|
2170
2222
|
provenance issuer configured, or its keys could not be read — not a forgery
|
|
2171
2223
|
signal); `pending` means the verdict has not been computed yet.
|
|
2172
2224
|
|
|
2225
|
+
A `pending` row is one still waiting to be minted — the attestation was signed
|
|
2226
|
+
at build time, but attaching its identity token has not completed yet. Those
|
|
2227
|
+
rows carry a **Retry** button that asks your orchestrator to mint that run's
|
|
2228
|
+
outstanding attestations immediately, and the page header offers **Retry
|
|
2229
|
+
pending** to do the same across every pending run. Only one retry runs at a
|
|
2230
|
+
time — the other retry buttons are unavailable until it finishes.
|
|
2231
|
+
|
|
2232
|
+
Retrying is safe to repeat while the mint is only temporarily unavailable: the
|
|
2233
|
+
row stays pending and the next retry tries again. A mint that is definitively
|
|
2234
|
+
rejected — for example the run's records are no longer there to bind the
|
|
2235
|
+
attestation to — is terminal: the row stops being retried, and re-arming it is
|
|
2236
|
+
an operator action (`kici-admin attestations retry --include-rejected`).
|
|
2237
|
+
|
|
2173
2238
|
Opening a row leads to the **attestation detail page**:
|
|
2174
2239
|
|
|
2175
2240
|
<!-- help:attestation-detail#browsing-attestations-across-runs -->
|
|
@@ -2179,13 +2244,14 @@ This page shows the parsed provenance for one attestation.
|
|
|
2179
2244
|
- **Builder identity, source, and build type** come from the signed SLSA statement.
|
|
2180
2245
|
- The **stored badge** is the verdict recorded at build time; **Re-verify** runs the check live in your browser against the current signing keys.
|
|
2181
2246
|
- **Download** exports the signed bundle for offline verification with `kici verify-attestation`.
|
|
2247
|
+
|
|
2182
2248
|
<!-- /help:attestation-detail -->
|
|
2183
2249
|
|
|
2184
2250
|
## See also
|
|
2185
2251
|
|
|
2186
2252
|
- [SDK runtime reference](https://docs.kici.dev/user/sdk/runtime/) — the `ctx.attestProvenance` and
|
|
2187
2253
|
`ctx.kici.oidc.token` step APIs in full.
|
|
2188
|
-
- [CLI reference](https://docs.kici.dev/user/cli-
|
|
2254
|
+
- [CLI reference](https://docs.kici.dev/user/cli/notifications-and-diagnostics/#kici-verify-attestation) — every
|
|
2189
2255
|
`kici verify-attestation` flag and exit code.
|
|
2190
2256
|
|
|
2191
2257
|
---
|
|
@@ -2198,11 +2264,11 @@ KiCI provides an explicit secrets API that gives workflow steps controlled acces
|
|
|
2198
2264
|
|
|
2199
2265
|
## Overview
|
|
2200
2266
|
|
|
2201
|
-
Secrets are managed per-
|
|
2267
|
+
Secrets are managed per-context in the orchestrator (see [operator docs](https://docs.kici.dev/operator/orchestrator/configuration) for setup). When a job runs with a `context` binding, the agent receives the secret keys available for that context but does **not** inject their values into the step's process environment. Instead, steps access secrets through the `ctx.secrets` API.
|
|
2202
2268
|
|
|
2203
2269
|
This design prevents accidental secret leakage through child processes, log output, or error messages. Only secrets you explicitly request are loaded into memory.
|
|
2204
2270
|
|
|
2205
|
-
A job can bind several
|
|
2271
|
+
A job can bind several contexts with `contexts: ['staging', 'my-testing']`; the secret keys from all bound contexts are merged in array order, with a later context's value winning on a key collision. See [Multiple contexts per job](https://docs.kici.dev/user/contexts/#multiple-contexts-per-job).
|
|
2206
2272
|
|
|
2207
2273
|
## Where secret values come from
|
|
2208
2274
|
|
|
@@ -2213,7 +2279,7 @@ Secret values are written either through the dashboard or through `kici-admin` r
|
|
|
2213
2279
|
A fresh orchestrator starts in **permissive** mode: both surfaces are available.
|
|
2214
2280
|
|
|
2215
2281
|
- **Dashboard:** Settings → Secrets → pick a scope → enter the secret name and value.
|
|
2216
|
-
- **CLI:** `kici-admin secret set
|
|
2282
|
+
- **CLI:** `kici-admin secret set <orgId> <scope> <KEY>` against the orchestrator's HTTP admin API.
|
|
2217
2283
|
|
|
2218
2284
|
Use whichever fits the workflow — most small teams stay on the dashboard; ops engineers and CI scripts use the CLI.
|
|
2219
2285
|
|
|
@@ -2221,7 +2287,7 @@ Use whichever fits the workflow — most small teams stay on the dashboard; ops
|
|
|
2221
2287
|
|
|
2222
2288
|
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:
|
|
2223
2289
|
|
|
2224
|
-
- The dashboard's "Add secret" / "Edit value" controls render with a lock icon.
|
|
2290
|
+
- The dashboard's "Add secret" / "Edit value" controls render with a lock icon. Hovering or keyboard-focusing the lock shows a tooltip with the exact `kici-admin secret set` invocation needed. The control itself is inert, so there is nothing to click.
|
|
2225
2291
|
- The dashboard's secrets page still lists secret **names**, scopes, and bindings — only the value-entry path moves to the CLI.
|
|
2226
2292
|
- `kici-admin secret set` becomes the single entry point for new and updated secret values.
|
|
2227
2293
|
|
|
@@ -2229,26 +2295,30 @@ This configuration is common for SOC2-prep and regulated workloads, where the cu
|
|
|
2229
2295
|
|
|
2230
2296
|
### CLI input modes
|
|
2231
2297
|
|
|
2232
|
-
`kici-admin secret set` accepts five input modes
|
|
2298
|
+
`kici-admin secret set` takes the target as three positional arguments — `<orgId> <scope> <key>` — and accepts five input modes; pick the one that fits your workflow:
|
|
2233
2299
|
|
|
2234
2300
|
```bash
|
|
2235
2301
|
# Interactive prompt (default when stdin is a TTY). No echo, no shell history.
|
|
2236
|
-
kici-admin secret set
|
|
2302
|
+
kici-admin secret set my-org production DB_PASSWORD --prompt
|
|
2237
2303
|
|
|
2238
2304
|
# Pipe from another tool (default when stdin is not a TTY).
|
|
2239
|
-
pass show prod/db | kici-admin secret set
|
|
2305
|
+
pass show prod/db | kici-admin secret set my-org production DB_PASSWORD --from-stdin
|
|
2240
2306
|
|
|
2241
2307
|
# Read from a file (handy after `sops -d` to a tmpfile).
|
|
2242
|
-
kici-admin secret set
|
|
2308
|
+
kici-admin secret set my-org production DB_PASSWORD --from-file ./db.pass
|
|
2243
2309
|
|
|
2244
2310
|
# Read from a named environment variable (CI-friendly).
|
|
2245
2311
|
KICI_SECRET_VALUE=$(my-secrets-fetcher prod db) \
|
|
2246
|
-
kici-admin secret set
|
|
2312
|
+
kici-admin secret set my-org production DB_PASSWORD --from-env KICI_SECRET_VALUE
|
|
2247
2313
|
|
|
2248
2314
|
# Direct argv — discouraged. Prints a stderr warning ("visible in shell history").
|
|
2249
|
-
kici-admin secret set
|
|
2315
|
+
kici-admin secret set my-org production DB_PASSWORD --value "<plaintext>"
|
|
2250
2316
|
```
|
|
2251
2317
|
|
|
2318
|
+
When the scope is a deployment context, a flag-based sugar form is also accepted:
|
|
2319
|
+
`kici-admin secret set --org my-org --context production --key DB_PASSWORD --prompt`.
|
|
2320
|
+
The two forms are mutually exclusive — mixing them is refused.
|
|
2321
|
+
|
|
2252
2322
|
Two cross-cutting flags help every mode:
|
|
2253
2323
|
|
|
2254
2324
|
- `--confirm-fingerprint <hex>` — pre-compute SHA-256 of the value and pass it. The CLI rejects the call if the value's fingerprint doesn't match. Catches paste corruption.
|
|
@@ -2270,7 +2340,7 @@ export default workflow('deploy', {
|
|
|
2270
2340
|
jobs: [
|
|
2271
2341
|
job('deploy', {
|
|
2272
2342
|
runsOn: 'default',
|
|
2273
|
-
|
|
2343
|
+
context: 'production',
|
|
2274
2344
|
steps: [
|
|
2275
2345
|
step('deploy', async (ctx) => {
|
|
2276
2346
|
const token = await ctx.secrets.get('DEPLOY_TOKEN');
|
|
@@ -2394,7 +2464,7 @@ export default workflow('deploy', {
|
|
|
2394
2464
|
jobs: [
|
|
2395
2465
|
job('decrypt-and-deploy', {
|
|
2396
2466
|
runsOn: 'default',
|
|
2397
|
-
|
|
2467
|
+
context: 'production',
|
|
2398
2468
|
steps: [
|
|
2399
2469
|
step('decrypt', async (ctx) => {
|
|
2400
2470
|
const ageKeys = ctx.secrets.list().filter((k) => k.startsWith('AGE_KEY_'));
|
|
@@ -2426,7 +2496,7 @@ export default workflow('deploy', {
|
|
|
2426
2496
|
jobs: [
|
|
2427
2497
|
job('decrypt-and-deploy', {
|
|
2428
2498
|
runsOn: 'default',
|
|
2429
|
-
|
|
2499
|
+
context: 'production',
|
|
2430
2500
|
steps: [
|
|
2431
2501
|
step('decrypt-to-env', async (ctx) => {
|
|
2432
2502
|
await ctx.secrets.exposeFile('SOPS_AGE_KEY_FILE', {
|