@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.
Files changed (199) hide show
  1. package/dist/auth/headless-detect.d.ts +14 -5
  2. package/dist/auth/headless-detect.js +50 -9
  3. package/dist/cli-banner.d.ts +6 -3
  4. package/dist/cli-banner.js +13 -5
  5. package/dist/cli.js +180 -48
  6. package/dist/commands/check-mode.d.ts +1 -1
  7. package/dist/commands/compile.js +31 -7
  8. package/dist/commands/diagnostics.js +14 -2
  9. package/dist/commands/doctor.d.ts +64 -0
  10. package/dist/commands/doctor.js +413 -0
  11. package/dist/commands/endpoints.js +6 -0
  12. package/dist/commands/fixture.js +13 -0
  13. package/dist/commands/hook.js +3 -2
  14. package/dist/commands/index.d.ts +13 -1
  15. package/dist/commands/index.js +10 -3
  16. package/dist/commands/init-host-os.d.ts +34 -0
  17. package/dist/commands/init-host-os.js +44 -0
  18. package/dist/commands/init.d.ts +4 -0
  19. package/dist/commands/init.js +160 -37
  20. package/dist/commands/local-trust-root.d.ts +6 -0
  21. package/dist/commands/local-trust-root.js +52 -0
  22. package/dist/commands/local.d.ts +65 -0
  23. package/dist/commands/local.js +206 -0
  24. package/dist/commands/login.d.ts +2 -0
  25. package/dist/commands/login.js +41 -1
  26. package/dist/commands/logout.js +7 -0
  27. package/dist/commands/notifications.d.ts +61 -0
  28. package/dist/commands/notifications.js +274 -0
  29. package/dist/commands/preview.js +9 -2
  30. package/dist/commands/run-banner.d.ts +31 -0
  31. package/dist/commands/run-banner.js +25 -0
  32. package/dist/commands/run-routed.d.ts +53 -0
  33. package/dist/commands/run-routed.js +185 -0
  34. package/dist/commands/run.d.ts +0 -17
  35. package/dist/commands/run.js +44 -58
  36. package/dist/commands/runs/artifacts/download.d.ts +24 -0
  37. package/dist/commands/runs/artifacts/download.js +154 -0
  38. package/dist/commands/runs/artifacts/list.d.ts +5 -0
  39. package/dist/commands/runs/artifacts/list.js +47 -0
  40. package/dist/commands/runs/cancel.js +2 -2
  41. package/dist/commands/runs/list.js +8 -2
  42. package/dist/commands/secrets-list.d.ts +2 -2
  43. package/dist/commands/secrets-list.js +4 -4
  44. package/dist/commands/types.d.ts +3 -3
  45. package/dist/commands/types.js +4 -4
  46. package/dist/commands/verify-attestation.d.ts +3 -1
  47. package/dist/commands/verify-attestation.js +18 -7
  48. package/dist/errors/formatter.d.ts +14 -1
  49. package/dist/errors/formatter.js +13 -3
  50. package/dist/errors/index.d.ts +2 -1
  51. package/dist/errors/index.js +3 -2
  52. package/dist/errors/source-location.d.ts +20 -0
  53. package/dist/errors/source-location.js +45 -0
  54. package/dist/execution/executor.js +32 -12
  55. package/dist/fixtures/defaults/index.js +2 -1
  56. package/dist/generators/secrets-dts.d.ts +9 -9
  57. package/dist/generators/secrets-dts.js +12 -12
  58. package/dist/llm-context/llms-architecture.txt +50 -21
  59. package/dist/llm-context/llms-cli.txt +2667 -1454
  60. package/dist/llm-context/llms-features.txt +484 -414
  61. package/dist/llm-context/llms-full.txt +6703 -4687
  62. package/dist/llm-context/llms-getting-started.txt +311 -55
  63. package/dist/llm-context/llms-patterns.txt +17 -29
  64. package/dist/llm-context/llms-providers.txt +10 -1
  65. package/dist/llm-context/llms-sdk-runtime.txt +2013 -0
  66. package/dist/llm-context/llms-sdk.txt +596 -2157
  67. package/dist/llm-context/llms.txt +31 -17
  68. package/dist/local-plane/orchestrator-process.d.ts +70 -0
  69. package/dist/local-plane/orchestrator-process.js +129 -0
  70. package/dist/local-plane/os-mismatch-hint.d.ts +16 -0
  71. package/dist/local-plane/os-mismatch-hint.js +34 -0
  72. package/dist/local-plane/paths.d.ts +39 -0
  73. package/dist/local-plane/paths.js +54 -0
  74. package/dist/local-plane/plane-liveness.d.ts +68 -0
  75. package/dist/local-plane/plane-liveness.js +145 -0
  76. package/dist/local-plane/plane-manager.d.ts +193 -0
  77. package/dist/local-plane/plane-manager.js +480 -0
  78. package/dist/local-plane/plane-seed.d.ts +76 -0
  79. package/dist/local-plane/plane-seed.js +59 -0
  80. package/dist/local-plane/plane-trigger.d.ts +71 -0
  81. package/dist/local-plane/plane-trigger.js +110 -0
  82. package/dist/local-plane/platform-attach.d.ts +53 -0
  83. package/dist/local-plane/platform-attach.js +107 -0
  84. package/dist/local-plane/port-holder.d.ts +78 -0
  85. package/dist/local-plane/port-holder.js +227 -0
  86. package/dist/local-plane/postgres.d.ts +56 -0
  87. package/dist/local-plane/postgres.js +187 -0
  88. package/dist/local-plane/resolve-plane.d.ts +37 -0
  89. package/dist/local-plane/resolve-plane.js +76 -0
  90. package/dist/local-plane/run-follow.d.ts +61 -0
  91. package/dist/local-plane/run-follow.js +151 -0
  92. package/dist/local-plane/scaler-config.d.ts +68 -0
  93. package/dist/local-plane/scaler-config.js +132 -0
  94. package/dist/local-plane/secret-seed.d.ts +59 -0
  95. package/dist/local-plane/secret-seed.js +112 -0
  96. package/dist/local-plane/source-provider.d.ts +43 -0
  97. package/dist/local-plane/source-provider.js +159 -0
  98. package/dist/local-plane/trusted-routing.d.ts +32 -0
  99. package/dist/local-plane/trusted-routing.js +47 -0
  100. package/dist/lockfile/generator.d.ts +14 -1
  101. package/dist/lockfile/generator.js +103 -37
  102. package/dist/lockfile/index.d.ts +3 -1
  103. package/dist/lockfile/index.js +3 -2
  104. package/dist/lockfile/purity-diagnostics.d.ts +31 -0
  105. package/dist/lockfile/purity-diagnostics.js +52 -0
  106. package/dist/postinstall.js +2 -1
  107. package/dist/remote/artifact-extract.d.ts +8 -0
  108. package/dist/remote/artifact-extract.js +58 -0
  109. package/dist/remote/config.d.ts +21 -3
  110. package/dist/remote/config.js +24 -5
  111. package/dist/remote/dashboard-client.d.ts +46 -9
  112. package/dist/remote/dashboard-client.js +54 -11
  113. package/dist/remote/fs-case.d.ts +21 -0
  114. package/dist/remote/fs-case.js +45 -0
  115. package/dist/remote/history.d.ts +9 -3
  116. package/dist/remote/history.js +79 -10
  117. package/dist/remote/local-repo-identity.d.ts +1 -1
  118. package/dist/remote/local-repo-identity.js +2 -1
  119. package/dist/remote/notifications-client.d.ts +149 -0
  120. package/dist/remote/notifications-client.js +103 -0
  121. package/dist/remote/oauth.d.ts +13 -0
  122. package/dist/remote/oauth.js +31 -5
  123. package/dist/remote/output/summary.js +76 -14
  124. package/dist/remote/platform-client.d.ts +1 -1
  125. package/dist/remote/render.js +30 -9
  126. package/dist/{local-executor → remote}/secret-loader.d.ts +1 -1
  127. package/dist/{local-executor → remote}/secret-loader.js +4 -4
  128. package/dist/remote/secret-upload.d.ts +1 -1
  129. package/dist/remote/secret-upload.js +2 -2
  130. package/dist/remote/uploader.d.ts +1 -1
  131. package/dist/remote/uploader.js +7 -6
  132. package/dist/templates/agents-md.d.ts +1 -1
  133. package/dist/templates/agents-md.js +8 -8
  134. package/dist/templates/index.d.ts +1 -1
  135. package/dist/templates/index.js +2 -2
  136. package/dist/templates/package-json.d.ts +15 -0
  137. package/dist/templates/package-json.js +26 -5
  138. package/dist/test-runner/dry-run.d.ts +2 -1
  139. package/dist/test-runner/dry-run.js +18 -1
  140. package/dist/test-runner/event-types.d.ts +2 -0
  141. package/dist/test-runner/event-types.js +3 -0
  142. package/dist/test-runner/job-executor.d.ts +1 -0
  143. package/dist/test-runner/job-executor.js +82 -44
  144. package/dist/test-runner/output-formatter.d.ts +5 -2
  145. package/dist/test-runner/output-formatter.js +6 -4
  146. package/dist/test-runner/payload-builder.d.ts +7 -1
  147. package/dist/test-runner/payload-builder.js +11 -2
  148. package/dist/test-runner/rule-evaluator.d.ts +7 -3
  149. package/dist/test-runner/rule-evaluator.js +13 -12
  150. package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
  151. package/dist/test-runner/step-context.d.ts +23 -5
  152. package/dist/test-runner/step-context.js +29 -119
  153. package/dist/types.d.ts +26 -7
  154. package/dist/types.js +4 -2
  155. package/dist/validation/index.d.ts +2 -0
  156. package/dist/validation/index.js +2 -1
  157. package/dist/validation/typecheck.d.ts +28 -0
  158. package/dist/validation/typecheck.js +110 -0
  159. package/dist/validation/validator.d.ts +9 -4
  160. package/dist/validation/validator.js +38 -44
  161. package/package.json +13 -13
  162. package/sbom.spdx.json +9311 -1528
  163. package/dist/assets/api-TJJVHrjC.json +0 -118
  164. package/dist/assets/descriptor-BTtjzN9L.json +0 -1382
  165. package/dist/assets/package-BpQF9kR8.json +0 -74
  166. package/dist/assets/package-Ceo2h27X.json +0 -89
  167. package/dist/assets/source_context-D0atuL28.json +0 -20
  168. package/dist/assets/type-BFqO8SCZ.json +0 -202
  169. package/dist/commands/cancel.d.ts +0 -22
  170. package/dist/commands/detect-package-manager.d.ts +0 -42
  171. package/dist/commands/held-run-resolve.d.ts +0 -50
  172. package/dist/commands/status.d.ts +0 -34
  173. package/dist/commands/test.d.ts +0 -88
  174. package/dist/errors/codes.d.ts +0 -25
  175. package/dist/local-executor/dag-scheduler.d.ts +0 -44
  176. package/dist/local-executor/dag-scheduler.js +0 -183
  177. package/dist/local-executor/index.d.ts +0 -23
  178. package/dist/local-executor/index.js +0 -378
  179. package/dist/local-executor/job-runner.d.ts +0 -47
  180. package/dist/local-executor/job-runner.js +0 -411
  181. package/dist/local-executor/materializer.d.ts +0 -44
  182. package/dist/local-executor/materializer.js +0 -132
  183. package/dist/local-executor/output-streamer.d.ts +0 -31
  184. package/dist/local-executor/output-streamer.js +0 -168
  185. package/dist/local-executor/payload-generator.d.ts +0 -16
  186. package/dist/local-executor/payload-generator.js +0 -138
  187. package/dist/local-executor/picker.d.ts +0 -33
  188. package/dist/local-executor/picker.js +0 -109
  189. package/dist/local-executor/to-event-payload.d.ts +0 -16
  190. package/dist/local-executor/to-event-payload.js +0 -21
  191. package/dist/local-executor/types.d.ts +0 -93
  192. package/dist/local-executor/types.js +0 -2
  193. package/dist/local-executor/workflow-lock.d.ts +0 -82
  194. package/dist/local-executor/workflow-lock.js +0 -0
  195. package/dist/package-F7UXSDHW.json +0 -74
  196. package/dist/remote/client.d.ts +0 -210
  197. package/dist/remote/observer.d.ts +0 -81
  198. package/dist/test-runner/summary.d.ts +0 -7
  199. /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, environments, secrets, approvals, provenance, events.
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 the `environments:write` or `ci_trust:write` permission. Use it when you want a manual gate without restricting who may release it.
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 environment via required reviewers (see [Environments](https://docs.kici.dev/user/environments/#required-reviewers)). When both apply to the same job, all clauses from both sources must be satisfied before the job is released. The two funnel into one held-element mechanism, so the dashboard queue and `kici approve` work the same way regardless of which source held the element.
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 apply-migration
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-reference/#kici-approve) for the full command reference.
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
- You can also approve from the dashboard approval queue. See [Dashboard](https://docs.kici.dev/user/dashboard/environments-and-secrets/#approval-queue).
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
- - [Environments](https://docs.kici.dev/user/environments/) — operator-required reviewers on protected environments.
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 [Environments](https://docs.kici.dev/user/environments/#concurrency-groups)), where the compiler performs purity analysis and can inline pure functions for orchestrator-side evaluation.
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
- environment: 'staging',
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 environment protection
457
+ ## Interaction with context protection
468
458
 
469
- When a workflow has both `concurrency` and `environment` protection rules:
459
+ When a workflow has both `concurrency` and `context` protection rules:
470
460
 
471
- 1. Environment protection gates (required reviewers, wait timer) apply first
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 [Environments — concurrency groups](https://docs.kici.dev/user/environments/#concurrency-groups) for details.
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` honors workflow-level `concurrency` per-machine, per-user. The `group` callback is evaluated against the simulated event identically to the remote orchestrator path; `cancelInProgress` carries the same semantics — `true` interrupts the holder via `SIGTERM` (escalating to `SIGKILL` after a grace window) and proceeds with the new run, while `false` queues the new invocation in FIFO order until the holder finishes.
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
- Lock files live under `$XDG_RUNTIME_DIR/kici-local-locks/` on Linux, falling back to `os.tmpdir()/kici-local-locks-<uid>/`. A workflow whose `group` callback throws aborts the run with a clear error rather than running unprotected. See [`kici run local` Concurrency enforcement](https://docs.kici.dev/user/cli-reference/#concurrency-enforcement) for the `KICI_LOCAL_LOCK_KILL_GRACE_MS` override and the diagnostic output emitted while contending on a busy lock.
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
- The `kici run local --concurrency <n>` flag is a separate concept — it caps **job-level** parallelism within a single run (how many jobs from one workflow run at once), not cross-run serialization.
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
- ## Dashboard
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/environments/
491
+ Source: https://docs.kici.dev/user/contexts/
724
492
 
725
- <!-- help:environments-list#overview -->
493
+ <!-- help:contexts-list#overview -->
726
494
 
727
- Environments are named deployment targets (like staging or production) that control where your workflow jobs run. Each environment can have its own variables, secrets, and protection rules to gate deployments.
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:environments-list -->
497
+ <!-- /help:contexts-list -->
730
498
 
731
- <!-- help:environments-protection#protection-rules -->
499
+ <!-- help:contexts-protection#protection-rules -->
732
500
 
733
- Protection rules control when jobs targeting an environment can execute.
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:environments-protection -->
510
+ <!-- /help:contexts-protection -->
743
511
 
744
- Environments represent deployment targets like `staging`, `production`, or `review/PR-*`. Each environment can have its own variables, bound secrets, and protection rules that control when and how jobs targeting that environment can execute.
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
- An environment in KiCI provides:
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 environment via scope bindings
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 environment property
525
+ ### Job-level context property
758
526
 
759
- The `environment` property is set on a job, not a workflow or step:
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
- environment: 'staging',
537
+ context: 'staging',
770
538
  steps: [
771
539
  step('deploy', async (ctx) => {
772
- // ctx.environment is the resolved environment name
773
- console.log(`Deploying to ${ctx.environment}`);
774
- // ctx.secrets provides async get/expose/has methods for environment-bound secrets
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.environment}`;
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 environments
554
+ ### Dynamic contexts
787
555
 
788
- The environment name can be a string or a function (sync or async) for dynamic environments (e.g., per-PR review environments). The function receives the normalized event envelope, with the raw provider body nested at `event.payload`:
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
- environment: (event) => `review/PR-${event.payload.pull_request.number}`,
561
+ context: (event) => `review/PR-${event.payload.pull_request.number}`,
794
562
  steps: [
795
563
  step('deploy', async (ctx) => {
796
- // ctx.environment is 'review/PR-123' (resolved at runtime)
797
- await ctx.$`deploy-preview --env ${ctx.environment}`;
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 environments that match a glob pattern (e.g., `review/*`) inherit the pattern's configuration, variables, and protection rules.
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 environments per job
573
+ ### Multiple contexts per job
806
574
 
807
- A job can bind more than one environment with `environments`, an ordered array. This lets a single job draw secrets and variables from several environments at once — for example a shared `staging` environment plus a `my-testing` environment that carries test-only variables:
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
- environments: ['staging', 'my-testing'],
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 environments
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
- - `environment` (singular) and `environments` (array) are mutually exclusive — setting both is a compile error. `environment: 'staging'` is exactly equivalent to `environments: ['staging']`.
823
- - Each array entry is a static name or a function of the event, resolved per element exactly like a single dynamic environment.
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 environments 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 environment, the later entry in the array wins. With `environments: ['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 environment.
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 environment's gates — adding an environment can never loosen access. Branch restrictions, trigger-type filters, and repo patterns must pass for all environments; the minimum trust tier is the most restrictive across them; required reviewers are the union of all environments' reviewers; the wait timer is the longest; and the hold expiry is the shortest. If a run is gated out, the rejection names which environment 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.
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 environment never rejects the run. Any bound environment 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 environment(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 `environments: ['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 environment 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/).
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 environments contribute nothing at dispatch.** At dispatch time a bound environment name with no matching configured environment (and no matching glob environment) simply adds no variables, secrets, or protection rules — the job still runs, exactly as a single dynamic environment resolving to an as-yet-unconfigured name does today.
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-environment binding: a bound environment that does not exist, a disabled one, or two environments 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 environments, and the rule — for example `unsatisfiable environment binding: job 'deploy' binds environments [staging, my-testing] with mutually exclusive branch restrictions (no value satisfies all bound environments)`. Bindings whose restrictions use globs are undecidable at registration and fall through to the dispatch-time gate check instead.
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
- environment: 'production',
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 an environment. For workflow-level concurrency (which applies to all jobs in a workflow), see [Concurrency groups](https://docs.kici.dev/user/concurrency/).
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 environment:
627
+ Control concurrent deployments to the same context:
860
628
 
861
629
  ```typescript
862
630
  job('deploy', {
863
631
  runsOn: 'default',
864
- environment: 'production',
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 environment name is used as the default concurrency group. For a job bound to multiple environments, the default is the **first** bound environment's name.
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 | Type | Description |
881
- | ----------------- | ------------------------------------- | ----------------------------------------------------------------------------------------- |
882
- | `ctx.environment` | `string \| undefined` | Resolved environment name (undefined for jobs without environment) |
883
- | `ctx.env` | `Record<string, string \| undefined>` | Environment variables (merged from system, org, source, and job-level `env`) |
884
- | `ctx.secrets` | `StepSecretsTyped` | Async accessor for bound secrets (get, expose, has, getMeta, list, mountFile, exposeFile) |
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 an environment, variables are merged in this order (last wins):
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 environment vars** -- from the dashboard, managed per-environment
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/environments/#step-context) section above.
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
- Environments can have protection rules that gate job execution:
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 an environment:
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-reference/#kici-approve) command, or the API. Held runs expire after a configurable timeout.
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 (unknown contributor, workflow modification, or trust policy violation), it enters the security approval queue. This is separate from environment-level approval queues.
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
- Security holds expire after a configurable timeout (default 1 hour).
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 an environment:
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 environments
769
+ ### Creating contexts
1000
770
 
1001
- Navigate to **Settings > Environments** in the dashboard. Click **New environment** to choose the environment name and type (Fixed or Glob).
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 environment name, like `staging` or `production`
1004
- - **Glob** -- applies to any environment name a job declares that matches the pattern, e.g. `review/*` matches a job with `environment: 'review/PR-123'`
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 environments list shows each environment's type, whether test runs may use it (the `allowLocalExecution` flag -- see the [testing guide](https://docs.kici.dev/user/testing-guide/)), and whether it is enabled.
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
- ### Environment detail page
778
+ ### Context detail page
1009
779
 
1010
- Each environment has four tabs:
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 environment.
788
+ 4. **History** -- view filtered runs targeting this context.
1019
789
 
1020
- ### Bound environments on runs
790
+ ### Bound contexts on runs
1021
791
 
1022
- A job's bound deployment environments 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-environment job the chips read left-to-right in merge order — later environments override earlier ones on key collisions. A `(dynamic)` chip marks an environment whose name is computed at runtime; it resolves to the real name once the run starts. A job that binds a single environment shows one chip; a job that binds none shows no chip.
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-environment binding is gated out, the run's failure banner names which environment and which rule rejected it (the same all-must-pass detail surfaced by `kici runs show <run-id>`).
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 environments via bindings:
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 environment binding checkboxes
1031
- - **Environment-centric view** (inside environment detail): bound scopes, resolved secrets, add binding
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 environments) and `EnvironmentSecrets` (per-environment key unions):
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 EnvironmentSecrets {
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. `EnvironmentSecrets` maps each environment to its available secret key names as a string union. Dynamic environments fall back to the full `KnownSecretKeys` union.
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's name:
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.name, {
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 _Workflow authors_ setting. |
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
- | Workflow authors | 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. |
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 _Workflow authors_ 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:
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` work on both local and remote runs:
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 push
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: exit non-zero (2) locally, or fail the run remotely,
1658
- # when any step reports drift. Use this as a CI gate ("fail the build if prod
1659
- # has drifted").
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 `<environment>:<secret-name>` syntax. The orchestrator resolves the token at dispatch time and the agent applies it for one `npm install` only.
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
- environment: 'production',
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 `<environment>:<secret-name>`. The orchestrator looks up the secret in the named environment via the per-environment secret resolver. The bare name **must not** contain a colon.
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
- environment: 'production',
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` environment'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}`.
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
- environment: 'production',
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
- environment: 'production',
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` environment.
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-environment scoping.** Every `tokenSecret` and `installEnv` entry is qualified with an environment name. The orchestrator runs the same protection-rule pipeline (branch / trust / concurrency / reviewer / wait-timer) against each named environment **before** resolving any secret, so a workflow that wants a `production` token from a feature branch is rejected exactly like a job that tries to deploy to `production` from a feature branch. A reviewer-gated install environment **pauses** the whole workflow dispatch as a workflow-scoped held run instead of resolving the token — see [Reviewer-gated installs](https://docs.kici.dev/user/private-registries/#reviewer-gated-installs) below.
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 environment lacks an explicit `requiredTrustTier`.
1897
- - **Lifecycle scripts disabled.** Whenever a private registry is in scope, the agent runs the install with `--ignore-scripts` (npm or pnpm alike). A malicious `preinstall` / `postinstall` hook in committed `package.json` cannot read the synthesized token env vars, even though they exist in the install subprocess. For a pnpm workspace, the agent builds your in-repo dependency closure as a separate step **after** the install's auth is torn down, so build scripts never see the tokens either.
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 environment 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.
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 environment or a branch the environment forbids) still fails the dispatch loudly with a clear reason, exactly as before — the orchestrator never dispatches a run with an unresolved install token.
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 environment. Reject reasons enumerate the failure mode: `malformed_ref`, `invalid_url_scheme`, `env_not_found`, `protection_rule_block`, `missing_token`, `missing_install_env`, etc. |
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 `<environment>:<secret-name>` values referenced by `tokenSecret` / `installEnv`.
1932
- - [Environments](https://docs.kici.dev/user/environments/) — protection rules (`branch_restrictions`, `requires_review`, `minimum_trust`) that the install gate inherits.
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 the KiCI platform that binds
1962
- the signature to the build identity. The token's identity claims
1963
- (`repository`, `ref`, `sha`, run/job ids) are derived by the platform from 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 platform's published
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. Calling it
2017
- during local execution rejects with a clear error.
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 platform from the run context, so a step cannot
2035
- spoof them. The returned token value is automatically masked in step logs, and
2036
- the step never holds platform credentials — the request is relayed through the
2037
- orchestrator, which mints the token on the step's behalf. Like
2038
- `attestProvenance`, it is only available inside a running job step.
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 the **KiCI platform's provenance issuer** — the same hosted
2055
- KiCI platform you `kici login` against. KiCI attestations are issued by, and
2056
- verified against, that one issuer; there are no competing "roots" to choose
2057
- between. So the answer to "shouldn't I just use KiCI as the trust root?" is yes
2058
- and that's the **default**: omit `--trust-root` and the verifier checks the
2059
- bundle against the hosted KiCI platform automatically. You only pass
2060
- `--trust-root` to verify against a different environment or, more commonly, an
2061
- offline `{ issuer, jwks }` file for air-gapped checks.
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 the hosted KiCI platform (no --trust-root needed):
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-reference/#kici-verify-attestation).
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-reference/#kici-verify-attestation) — every
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-environment in the orchestrator (see [operator docs](https://docs.kici.dev/operator/orchestrator/configuration) for setup). When a job runs with an `environment` binding, the agent receives the secret keys available for that environment but does **not** inject their values into the step's process environment. Instead, steps access secrets through the `ctx.secrets` API.
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 environments with `environments: ['staging', 'my-testing']`; the secret keys from all bound environments are merged in array order, with a later environment's value winning on a key collision. See [Multiple environments per job](https://docs.kici.dev/user/environments/#multiple-environments-per-job).
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 --scope <scope> <KEY>` against the orchestrator's HTTP admin API.
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. Clicking them shows a tooltip with the exact `kici-admin secret set` invocation needed.
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 pick the one that fits your workflow:
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 --scope production DB_PASSWORD --prompt
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 --scope production DB_PASSWORD --from-stdin
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 --scope production DB_PASSWORD --from-file ./db.pass
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 --scope production DB_PASSWORD --from-env KICI_SECRET_VALUE
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 --scope production DB_PASSWORD --value "<plaintext>"
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
- environment: 'production',
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
- environment: 'production',
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
- environment: 'production',
2499
+ context: 'production',
2430
2500
  steps: [
2431
2501
  step('decrypt-to-env', async (ctx) => {
2432
2502
  await ctx.secrets.exposeFile('SOPS_AGE_KEY_FILE', {