@kici-dev/compiler 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +37 -7
- package/dist/commands/approve.d.ts +12 -0
- package/dist/commands/approve.js +5 -2
- package/dist/commands/compile.js +5 -15
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/endpoints.js +4 -6
- package/dist/commands/held-run-client.d.ts +21 -1
- package/dist/commands/held-run-client.js +34 -15
- package/dist/commands/hook.js +22 -20
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +2 -1
- package/dist/commands/init.d.ts +9 -2
- package/dist/commands/init.js +43 -16
- package/dist/commands/login.js +1 -1
- package/dist/commands/orchestrators.js +3 -2
- package/dist/commands/preview.js +1 -8
- package/dist/commands/reject.d.ts +12 -0
- package/dist/commands/reject.js +5 -2
- package/dist/commands/report/collect.d.ts +82 -0
- package/dist/commands/report/collect.js +234 -0
- package/dist/commands/report/identity.d.ts +48 -0
- package/dist/commands/report/identity.js +49 -0
- package/dist/commands/report/index.d.ts +63 -0
- package/dist/commands/report/index.js +119 -0
- package/dist/commands/report/upload.d.ts +38 -0
- package/dist/commands/report/upload.js +64 -0
- package/dist/commands/run-hold-watch.js +2 -2
- package/dist/commands/run.js +6 -3
- package/dist/commands/runs/show.js +80 -1
- package/dist/commands/types.js +51 -9
- package/dist/errors/formatter.d.ts +2 -4
- package/dist/errors/formatter.js +1 -3
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +2 -2
- package/dist/execution/sdk-alias.js +4 -2
- package/dist/fixtures/compiler.js +2 -1
- package/dist/format.js +3 -3
- package/dist/generators/secrets-dts.d.ts +8 -3
- package/dist/generators/secrets-dts.js +3 -3
- package/dist/hooks/installer.js +2 -1
- package/dist/llm-context/llms-architecture.txt +59 -23
- package/dist/llm-context/llms-cli.txt +168 -34
- package/dist/llm-context/llms-features-execution.txt +2017 -0
- package/dist/llm-context/llms-features.txt +96 -1100
- package/dist/llm-context/llms-full.txt +3131 -1407
- package/dist/llm-context/llms-getting-started.txt +164 -31
- package/dist/llm-context/llms-patterns.txt +186 -7
- package/dist/llm-context/llms-providers.txt +15 -33
- package/dist/llm-context/llms-sdk-runtime.txt +62 -40
- package/dist/llm-context/llms-sdk.txt +284 -58
- package/dist/llm-context/llms.txt +23 -15
- package/dist/local-plane/paths.d.ts +15 -0
- package/dist/local-plane/paths.js +22 -1
- package/dist/local-plane/plane-manager.js +2 -2
- package/dist/local-plane/port-holder.js +1 -1
- package/dist/local-plane/postgres.d.ts +3 -16
- package/dist/local-plane/postgres.js +10 -15
- package/dist/lockfile/generator.d.ts +12 -0
- package/dist/lockfile/generator.js +182 -54
- package/dist/lockfile/index.d.ts +0 -2
- package/dist/lockfile/index.js +1 -2
- package/dist/postinstall.js +2 -1
- package/dist/remote/config.d.ts +2 -15
- package/dist/remote/config.js +2 -16
- package/dist/remote/dashboard-client.d.ts +39 -0
- package/dist/remote/dashboard-client.js +41 -0
- package/dist/remote/oauth.js +7 -5
- package/dist/remote/uploader.js +2 -2
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/dry-run.d.ts +1 -2
- package/dist/test-runner/dry-run.js +5 -20
- package/dist/test-runner/git-detector.js +2 -1
- package/dist/test-runner/job-executor.js +2 -1
- package/dist/test-runner/payload-builder.js +11 -17
- package/dist/types.d.ts +64 -10
- package/dist/types.js +7 -1
- package/dist/validation/validator.js +63 -6
- package/package.json +16 -11
- package/sbom.spdx.json +957 -900
- package/dist/lockfile/purity-analyzer.d.ts +0 -25
- package/dist/lockfile/purity-analyzer.js +0 -204
- package/dist/lockfile/purity-diagnostics.d.ts +0 -31
- package/dist/lockfile/purity-diagnostics.js +0 -52
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# KiCI Workflow features
|
|
1
|
+
# KiCI Workflow features: access and approval
|
|
2
2
|
|
|
3
|
-
This bundle covers:
|
|
3
|
+
This bundle covers: Deployment contexts, scoped secrets, private registries, approvals, provenance, dashboard and account access.
|
|
4
4
|
|
|
5
5
|
## Account and sign-in
|
|
6
6
|
|
|
@@ -196,6 +196,10 @@ Here `build-plan` runs, then the job pauses for a `dba` approval. On approval, `
|
|
|
196
196
|
|
|
197
197
|
Because a step-level hold keeps an agent and its workspace occupied for the whole human wait, prefer job- or workflow-level gates when you do not need prior-step state, and keep step-level timeouts short. See the [operator note on agent occupancy](https://docs.kici.dev/operator/approvals/#agent-occupancy-during-step-level-holds).
|
|
198
198
|
|
|
199
|
+
### Not available on organization-wide workflows
|
|
200
|
+
|
|
201
|
+
An `approval` gate applies to per-repository workflows only. A workflow whose trigger carries `repos:` — an [organization-wide workflow](https://docs.kici.dev/user/global-workflows/) — is dispatched by a path that never consults the gate, so the gate would not hold anything. `kici compile` refuses it with `error [E124]` at the workflow level and on any static job, rather than accepting a security control the workflow does not have. Drop the `approval`, or move the gated jobs into a workflow whose triggers carry no `repos:`. A job produced by a `dynamicJob` generator never passes through the compiler, so that one is caught at dispatch instead: the orchestrator logs an error naming the workflow and job, and runs it ungated.
|
|
202
|
+
|
|
199
203
|
## Drift gates (`when: 'drift'`)
|
|
200
204
|
|
|
201
205
|
A `when: 'drift'` gate is **step-scope only** and requires a [check/apply step](https://docs.kici.dev/user/idempotent-steps/). Instead of pausing unconditionally, it fires **between the step's `check` and `run`, only when `check` finds drift in apply mode** — exactly Terraform's plan→apply, scoped to one step. When the step is already in sync (no drift), nothing pauses and the step skips.
|
|
@@ -228,7 +232,7 @@ A `when: 'drift'` gate on a step without a `check` facet, or at job/workflow sco
|
|
|
228
232
|
|
|
229
233
|
## Mandatory vs. explicit gates
|
|
230
234
|
|
|
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
|
|
235
|
+
`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. A workflow-level `approval` is one of those sources. It gates every root job, so a root job that also binds a context with required reviewers must satisfy the workflow clauses, its own job clauses, and the context's reviewers. The sources 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
236
|
|
|
233
237
|
## Approving from the CLI
|
|
234
238
|
|
|
@@ -250,6 +254,23 @@ kici reject <run-id> --job deploy-production --reason "Wrong release branch"
|
|
|
250
254
|
|
|
251
255
|
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
256
|
|
|
257
|
+
### When a job is held twice
|
|
258
|
+
|
|
259
|
+
A job can carry an `approval` gate **and** a [security hold](https://docs.kici.dev/user/contexts/#security-approval-queue) at the same time. Two setups reach it: a fork pull request on an organization whose fork policy is `hold`, and a context that sets both required reviewers and `minimumTrust`.
|
|
260
|
+
|
|
261
|
+
The two are separate holds and **both** must be released before the job runs. They take different permissions: the approval hold needs `contexts:write` plus clause eligibility, the security hold needs `ci_trust:write`.
|
|
262
|
+
|
|
263
|
+
`--job` names both at once, so pick one with `--hold-type`:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
kici approve <run-id> --job deploy --hold-type reviewer
|
|
267
|
+
kici approve <run-id> --job deploy --hold-type security
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Pass `--hold <id>` when the command's error lists two holds that `--hold-type` still cannot separate. The pull request's `KiCI Security` check stays pending until both holds have ended, and its description names the second gate and the permission that clears it.
|
|
271
|
+
|
|
272
|
+
Each hold carries its own expiry, so the job is cancelled when the **first** one runs out.
|
|
273
|
+
|
|
253
274
|
### Inline approval and `--approve-all` in `kici run remote`
|
|
254
275
|
|
|
255
276
|
When you trigger a run with `kici run remote` and it pauses on a gate, the CLI prints the gate (and, for a drift gate, the computed drift) and — in an interactive terminal — prompts you to approve or reject right there, without leaving the watch. In a non-interactive shell it prints how to approve out of band and keeps watching.
|
|
@@ -272,217 +293,7 @@ You can also approve from the dashboard approval queue. See [Dashboard](https://
|
|
|
272
293
|
- [Contexts](https://docs.kici.dev/user/contexts/) — operator-required reviewers on protected contexts.
|
|
273
294
|
- [Approval gates (operator guide)](https://docs.kici.dev/operator/approvals/) — teams, the approval queue, expiry, and self-approval.
|
|
274
295
|
- [Approval gates (architecture)](https://docs.kici.dev/architecture/approvals/) — the unified hold model and the step-level round-trip.
|
|
275
|
-
|
|
276
|
-
---
|
|
277
|
-
|
|
278
|
-
## Concurrency groups
|
|
279
|
-
|
|
280
|
-
Source: https://docs.kici.dev/user/concurrency/
|
|
281
|
-
|
|
282
|
-
Concurrency groups prevent multiple workflow runs from executing in parallel when they target the same resource. Common use cases include preventing parallel deploys to the same environment or serializing database migrations.
|
|
283
|
-
|
|
284
|
-
## Basic usage
|
|
285
|
-
|
|
286
|
-
```typescript
|
|
287
|
-
import { workflow, job, step, push } from '@kici-dev/sdk';
|
|
288
|
-
|
|
289
|
-
export default workflow('deploy', {
|
|
290
|
-
on: push({ branches: ['main', 'staging'] }),
|
|
291
|
-
concurrency: {
|
|
292
|
-
group: (ctx) => `deploy-${ctx.branch}`,
|
|
293
|
-
cancelInProgress: true,
|
|
294
|
-
max: 1,
|
|
295
|
-
},
|
|
296
|
-
jobs: [
|
|
297
|
-
job('deploy', {
|
|
298
|
-
runsOn: 'linux',
|
|
299
|
-
steps: [
|
|
300
|
-
step('deploy', async ({ $ }) => {
|
|
301
|
-
await $`./deploy.sh`;
|
|
302
|
-
}),
|
|
303
|
-
],
|
|
304
|
-
}),
|
|
305
|
-
],
|
|
306
|
-
});
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
## Configuration
|
|
310
|
-
|
|
311
|
-
The `concurrency` option on a workflow accepts:
|
|
312
|
-
|
|
313
|
-
| Field | Type | Default | Description |
|
|
314
|
-
| ------------------ | -------- | -------- | ------------------------------------------ |
|
|
315
|
-
| `group` | Function | Required | Returns the concurrency group key string |
|
|
316
|
-
| `cancelInProgress` | boolean | `true` | Cancel older runs when a newer run arrives |
|
|
317
|
-
| `max` | number | `1` | Maximum concurrent runs in the same group |
|
|
318
|
-
|
|
319
|
-
### Group key function
|
|
320
|
-
|
|
321
|
-
The group key function receives a context with the branch name and event payload. Runs with the same group key are subject to concurrency limits.
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
// Per-branch concurrency (most common)
|
|
325
|
-
group: (ctx) => `deploy-${ctx.branch}`;
|
|
326
|
-
|
|
327
|
-
// Global concurrency (across all branches)
|
|
328
|
-
group: () => 'deploy';
|
|
329
|
-
|
|
330
|
-
// Per-target-branch concurrency
|
|
331
|
-
group: (ctx) => `deploy-${ctx.event.targetBranch ?? 'default'}`;
|
|
332
|
-
```
|
|
333
|
-
|
|
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.
|
|
335
|
-
|
|
336
|
-
## cancelInProgress mode
|
|
337
|
-
|
|
338
|
-
When `cancelInProgress: true`, a newer run supersedes older runs in the same group:
|
|
339
|
-
|
|
340
|
-
```
|
|
341
|
-
Run #1 starts deploying to main -> running
|
|
342
|
-
Run #2 arrives for deploy-main group -> Run #1 cancelled ("Superseded by run in concurrency group 'deploy-main'")
|
|
343
|
-
Run #2 continues -> running
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
This is the most common mode for deploy workflows -- you want the latest code deployed, not an outdated version.
|
|
347
|
-
|
|
348
|
-
The cancelled run:
|
|
349
|
-
|
|
350
|
-
- Receives a cancellation with reason "Superseded by run in concurrency group 'deploy-main'"
|
|
351
|
-
- Goes through the normal cancel flow (grace period, hooks if graceful)
|
|
352
|
-
- GitHub Check status updated to `cancelled` with the superseded reason
|
|
353
|
-
|
|
354
|
-
```typescript
|
|
355
|
-
workflow('deploy', {
|
|
356
|
-
concurrency: {
|
|
357
|
-
group: (ctx) => `deploy-${ctx.branch}`,
|
|
358
|
-
cancelInProgress: true,
|
|
359
|
-
},
|
|
360
|
-
jobs: [/* ... */],
|
|
361
|
-
});
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
## Queue mode
|
|
365
|
-
|
|
366
|
-
When `cancelInProgress: false`, newer runs will wait until older runs complete:
|
|
367
|
-
|
|
368
|
-
```
|
|
369
|
-
Run #1 starts deploying -> running
|
|
370
|
-
Run #2 arrives for same group -> queued ("Waiting for deploy-main (1 ahead)")
|
|
371
|
-
Run #1 completes -> success
|
|
372
|
-
Run #2 starts -> running
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
In queue mode, the agent that picked up the queued run **stays connected** to the orchestrator and parks on a long-poll wait. When the holder finishes (success, failure, or cancel), the orchestrator dequeues the FIFO-next entry and pushes a `proceed` notification over the same WebSocket; the queued agent then continues with normal step execution against the workspace it already has. The agent's slot is therefore held for the duration of the queue wait — bound by `KICI_CONCURRENCY_WAIT_TIMEOUT_MS` (default 1 hour).
|
|
376
|
-
|
|
377
|
-
```typescript
|
|
378
|
-
workflow('migrate-db', {
|
|
379
|
-
concurrency: {
|
|
380
|
-
group: () => 'migrations',
|
|
381
|
-
cancelInProgress: false,
|
|
382
|
-
max: 1,
|
|
383
|
-
},
|
|
384
|
-
jobs: [/* ... */],
|
|
385
|
-
});
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
The dashboard will show a "Queued" badge with the reason: "Waiting for deploy-main (1 ahead)".
|
|
389
|
-
|
|
390
|
-
## Max concurrent runs
|
|
391
|
-
|
|
392
|
-
The `max` field controls how many runs can execute simultaneously in the same group:
|
|
393
|
-
|
|
394
|
-
```typescript
|
|
395
|
-
// Allow up to 3 parallel test runs per branch
|
|
396
|
-
workflow('test', {
|
|
397
|
-
concurrency: {
|
|
398
|
-
group: (ctx) => `test-${ctx.branch}`,
|
|
399
|
-
cancelInProgress: false,
|
|
400
|
-
max: 3,
|
|
401
|
-
},
|
|
402
|
-
jobs: [/* ... */],
|
|
403
|
-
});
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
When `max: 1` (default), runs are fully serialized within the group.
|
|
407
|
-
|
|
408
|
-
## Group key examples
|
|
409
|
-
|
|
410
|
-
### Deploy per environment
|
|
411
|
-
|
|
412
|
-
```typescript
|
|
413
|
-
workflow('deploy', {
|
|
414
|
-
concurrency: {
|
|
415
|
-
group: (ctx) => `deploy-${ctx.branch}`,
|
|
416
|
-
cancelInProgress: true,
|
|
417
|
-
},
|
|
418
|
-
jobs: [
|
|
419
|
-
job('deploy-staging', {
|
|
420
|
-
runsOn: 'linux',
|
|
421
|
-
context: 'staging',
|
|
422
|
-
steps: [/* ... */],
|
|
423
|
-
}),
|
|
424
|
-
],
|
|
425
|
-
});
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
### Global singleton
|
|
429
|
-
|
|
430
|
-
```typescript
|
|
431
|
-
// Only one migration can run at a time, regardless of branch
|
|
432
|
-
workflow('migrate', {
|
|
433
|
-
concurrency: {
|
|
434
|
-
group: () => 'db-migration',
|
|
435
|
-
cancelInProgress: false,
|
|
436
|
-
},
|
|
437
|
-
jobs: [/* ... */],
|
|
438
|
-
});
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
### Environment-aware groups
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
// Serialize deploys per environment
|
|
445
|
-
workflow('deploy', {
|
|
446
|
-
concurrency: {
|
|
447
|
-
group: (ctx) => {
|
|
448
|
-
const env = ctx.branch === 'main' ? 'production' : 'staging';
|
|
449
|
-
return `deploy-${env}`;
|
|
450
|
-
},
|
|
451
|
-
cancelInProgress: true,
|
|
452
|
-
},
|
|
453
|
-
jobs: [/* ... */],
|
|
454
|
-
});
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
## Interaction with context protection
|
|
458
|
-
|
|
459
|
-
When a workflow has both `concurrency` and `context` protection rules:
|
|
460
|
-
|
|
461
|
-
1. Context protection gates (required reviewers, wait timer) apply first
|
|
462
|
-
2. Concurrency group check happens after protection gates pass
|
|
463
|
-
3. If the run is queued by concurrency, it keeps its protection approval
|
|
464
|
-
|
|
465
|
-
This means a run that passed approval won't need re-approval if it gets queued by concurrency.
|
|
466
|
-
|
|
467
|
-
## Cancelling queued runs
|
|
468
|
-
|
|
469
|
-
Queued runs can be cancelled before they start executing. The cancel request removes them from the queue immediately -- they don't go through the grace period since no step is running.
|
|
470
|
-
|
|
471
|
-
## Job-level concurrency groups
|
|
472
|
-
|
|
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.
|
|
474
|
-
|
|
475
|
-
## Local execution
|
|
476
|
-
|
|
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.
|
|
478
|
-
|
|
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.
|
|
480
|
-
|
|
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.
|
|
482
|
-
|
|
483
|
-
---
|
|
484
|
-
|
|
485
|
-
_Source: `packages/sdk/src/types.ts` (WorkflowOptions.concurrency, JobOptions.concurrencyGroup)_
|
|
296
|
+
- [Organization-wide workflows](https://docs.kici.dev/user/global-workflows/) — why an approval gate is refused there.
|
|
486
297
|
|
|
487
298
|
---
|
|
488
299
|
|
|
@@ -509,8 +320,6 @@ Available rules:
|
|
|
509
320
|
|
|
510
321
|
<!-- /help:contexts-protection -->
|
|
511
322
|
|
|
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.
|
|
513
|
-
|
|
514
323
|
## Overview
|
|
515
324
|
|
|
516
325
|
A context in KiCI provides:
|
|
@@ -568,7 +377,7 @@ job('deploy-review', {
|
|
|
568
377
|
});
|
|
569
378
|
```
|
|
570
379
|
|
|
571
|
-
A
|
|
380
|
+
A dynamic context function like the one above (see [Dynamic values](https://docs.kici.dev/user/dynamic-values/)) is resolved on the eval agent's init step before the job runs. Dynamic contexts that match a glob pattern (e.g., `review/*`) inherit the pattern's configuration, variables, and protection rules.
|
|
572
381
|
|
|
573
382
|
### Multiple contexts per job
|
|
574
383
|
|
|
@@ -592,7 +401,7 @@ job('deploy', {
|
|
|
592
401
|
|
|
593
402
|
**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.
|
|
594
403
|
|
|
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
|
|
404
|
+
**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, and required reviewers are the union of all contexts' reviewers. The wait timer is the longest, and the hold expiry is the shortest. If a rule rejects a job, that job is not dispatched. It still appears on the run, as a failed job whose reason names the context and the rule that rejected it. Read the reason with `kici runs show <run-id>`, or on the run detail page.
|
|
596
405
|
|
|
597
406
|
**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/).
|
|
598
407
|
|
|
@@ -690,6 +499,34 @@ Allowed branches: main, release/*
|
|
|
690
499
|
|
|
691
500
|
Jobs from other branches are rejected immediately with an error message.
|
|
692
501
|
|
|
502
|
+
**Most internally-triggered runs carry a branch.** KiCI starts these runs for itself: a [schedule](https://docs.kici.dev/user/sdk/triggers/) fire, a [custom event](https://docs.kici.dev/user/events/), a completion trigger, a failure batch, or an [invoke gate](https://docs.kici.dev/user/global-workflows/) summon. None comes from a branch push. A run with one source branch presents it:
|
|
503
|
+
|
|
504
|
+
- A **schedule** fire executes the default branch's workflow, so it presents the repository's default branch.
|
|
505
|
+
- A **`kiciEvent()` subscriber**, a **completion trigger** (`workflowComplete`, `jobComplete`) and an **invoke-gate summon** present the branch of the run behind them. That is the same branch the run behind them presented. For a pull-request run it is the branch the PR targets, not the contributor's own branch — so a subscriber of an event emitted by a PR against `main` presents `main`.
|
|
506
|
+
|
|
507
|
+
A branch restriction compares that branch against your patterns like any other run. So a nightly deploy bound to a `production` context restricted to `main` runs.
|
|
508
|
+
|
|
509
|
+
A branch restriction is not a trust control. It checks the branch a run presents, never where the code came from. A pull request against `main` presents `main`, whoever opened it. To gate on that, set a [minimum trust tier](https://docs.kici.dev/user/contexts/#minimum-trust) on the same context.
|
|
510
|
+
|
|
511
|
+
A run with no single source branch presents no branch, and a branch restriction then rejects it:
|
|
512
|
+
|
|
513
|
+
- A **failure batch** (`workflowsFailedBatch`). One accumulation window of failed runs causes it, on as many branches, so no one branch is its own.
|
|
514
|
+
- A **scaler event** (`kici.scaler.scale-up`, `kici.scaler.scale-down`). The orchestrator mints these itself, with no run behind them. See [autoscaling workflows](https://docs.kici.dev/user/workflows/autoscaling-workflows/).
|
|
515
|
+
- A **schedule** fire in a repository that has not pushed to its default branch since you upgraded KiCI. The default branch is captured when a push re-registers the workflows, so the first such push after the upgrade fixes it.
|
|
516
|
+
- Any event whose emitting run is no longer on record.
|
|
517
|
+
|
|
518
|
+
The rejection reason says so:
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
Context 'production' restricts branches: this internally-triggered run carries no
|
|
522
|
+
branch, so no branch restriction can be satisfied - a scheduled run gains its
|
|
523
|
+
branch after the next push to the default branch re-registers the workflow;
|
|
524
|
+
alternatively bind a context without a branch restriction, or restrict by trigger
|
|
525
|
+
type instead
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
To limit a context by how the run started instead of by branch, use a **trigger-type filter**: the trigger type is a real name (`schedule`, `kici_event`, `workflow_complete`, `job_complete`), so a filter that allows it works on these runs.
|
|
529
|
+
|
|
693
530
|
### Required reviewers
|
|
694
531
|
|
|
695
532
|
Require manual approval before a job can proceed:
|
|
@@ -714,41 +551,44 @@ The job waits for the specified duration before proceeding. Useful for staged ro
|
|
|
714
551
|
|
|
715
552
|
### Minimum trust
|
|
716
553
|
|
|
717
|
-
|
|
554
|
+
Hold a job whose run came from a fork:
|
|
718
555
|
|
|
719
556
|
```
|
|
720
|
-
Minimum trust:
|
|
557
|
+
Minimum trust: trusted
|
|
721
558
|
```
|
|
722
559
|
|
|
723
|
-
| Value | Effect
|
|
724
|
-
| --------- |
|
|
725
|
-
| `
|
|
726
|
-
| `
|
|
560
|
+
| Value | Effect |
|
|
561
|
+
| --------- | ---------------------------------------------------------- |
|
|
562
|
+
| `trusted` | Holds a run whose ref came from a fork |
|
|
563
|
+
| `known` | Same effect; the value is deprecated and removed at v1.0.0 |
|
|
564
|
+
| (unset) | No trust-based gating |
|
|
565
|
+
|
|
566
|
+
Both values block the same thing. Trust comes from the git ref, and that judgement has two answers: a ref in your repository is `trusted`, a ref from a fork is `unknown`. The value you declare still decides the wording of the hold reason.
|
|
567
|
+
|
|
568
|
+
When the gate holds a job, it enters the security approval queue. Someone with `ci_trust:write` or higher must approve it before execution proceeds.
|
|
727
569
|
|
|
728
|
-
|
|
570
|
+
A run that resolved **no** tier passes the gate. A pull request from a source with no fork model resolves none, and so does an internal run whose inheritance lookup failed. See [trust tiers on internal triggers](https://docs.kici.dev/user/events/#trust-tiers-on-internal-triggers) for the full table.
|
|
729
571
|
|
|
730
|
-
|
|
572
|
+
The trust tier also affects which lock file a pull-request run uses: a trusted ref evaluates the head lock file, a fork ref evaluates the base branch's. A fork run additionally carries no install or registry secrets, and its build-cache writes are confined to that run. So a fork pull request cannot change what CI does, and cannot read a private-registry token, whether or not you set this gate.
|
|
731
573
|
|
|
732
|
-
|
|
733
|
-
- **Known** -- identity-linked member or verified collaborator via provider API
|
|
734
|
-
- **Unknown** -- no identity link and no provider access, fork PRs
|
|
574
|
+
Set the gate on any context that carries a credential a fork run must not reach. See the [deployment checklist](https://docs.kici.dev/operator/security/security/#deployment-checklist-which-contexts-need-it).
|
|
735
575
|
|
|
736
|
-
|
|
576
|
+
An internally-triggered run carries a tier too. A schedule fire and the orchestrator's own lifecycle events are trusted. A `kiciEvent()` subscriber inherits the tier of the run that emitted the event, so a `minimumTrust` gate on the subscriber's context reads the **emitting** run's tier. See [trust tiers on internal triggers](https://docs.kici.dev/user/events/#trust-tiers-on-internal-triggers).
|
|
737
577
|
|
|
738
578
|
See the [CI security architecture docs](https://docs.kici.dev/architecture/security/ci-security/) for the full trust resolution flow.
|
|
739
579
|
|
|
740
580
|
### Security approval queue
|
|
741
581
|
|
|
742
|
-
|
|
582
|
+
A pull request held for security review enters the security approval queue. Two things put it there: the organization's fork switch set to `hold`, and a `minimumTrust` gate blocking a fork run. 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.
|
|
743
583
|
|
|
744
584
|
Held runs can be approved:
|
|
745
585
|
|
|
746
|
-
- Via the **dashboard**
|
|
586
|
+
- Via the **dashboard** on the [Approval queue](https://docs.kici.dev/user/dashboard/contexts-and-secrets/#approval-queue) page, which lists security and context holds together
|
|
747
587
|
- Via a PR comment: `/kici approve` (commenter must have `ci_trust:write+`)
|
|
748
588
|
|
|
749
|
-
A hold raised by the
|
|
589
|
+
A hold raised by the fork switch covers the whole pull request and uses the org's approval expiry (default 72 hours). A `minimumTrust` hold is raised by a context, so it uses that context's own hold expiry (default one hour). A job carrying both a reviewer approval hold and a security hold carries both expiries, and whichever comes first cancels the run.
|
|
750
590
|
|
|
751
|
-
While the
|
|
591
|
+
While the fork switch 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.
|
|
752
592
|
|
|
753
593
|
### Concurrency limits
|
|
754
594
|
|
|
@@ -764,6 +604,10 @@ The concurrency limit is a positive integer; leave it unset for unlimited concur
|
|
|
764
604
|
- **queue** -- new jobs wait in a FIFO queue (with configurable timeout, default 1 hour)
|
|
765
605
|
- **cancel-pending** -- pending (queued) jobs are cancelled when the limit is reached
|
|
766
606
|
|
|
607
|
+
The children of a matrix job count individually against the limit. A three-child
|
|
608
|
+
matrix bound to a context with a limit of two dispatches two children and applies
|
|
609
|
+
the strategy above to the third.
|
|
610
|
+
|
|
767
611
|
## Dashboard management
|
|
768
612
|
|
|
769
613
|
### Creating contexts
|
|
@@ -791,7 +635,7 @@ Each context has four tabs:
|
|
|
791
635
|
|
|
792
636
|
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.
|
|
793
637
|
|
|
794
|
-
|
|
638
|
+
A job a bound context rejects keeps its chips, and shows as failed because it never ran. Its failure reason names the context and the rule that rejected it. `kici runs show <run-id>` prints the same reason.
|
|
795
639
|
|
|
796
640
|
### Secrets management
|
|
797
641
|
|
|
@@ -844,857 +688,6 @@ This guide is split across the following pages:
|
|
|
844
688
|
|
|
845
689
|
---
|
|
846
690
|
|
|
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
|
-
```
|
|
1071
|
-
|
|
1072
|
-
---
|
|
1073
|
-
|
|
1074
|
-
## Event system
|
|
1075
|
-
|
|
1076
|
-
Source: https://docs.kici.dev/user/events/
|
|
1077
|
-
|
|
1078
|
-
KiCI supports two broad categories of workflow triggers: **git-based triggers** that work immediately, and **event-based triggers** that use a registration model. Understanding this distinction is key to working effectively with non-git triggers like schedules, custom events, and generic webhooks.
|
|
1079
|
-
|
|
1080
|
-
## Overview
|
|
1081
|
-
|
|
1082
|
-
Git-based triggers (`push()`, `pr()`, `tag()`, `comment()`, `review()`, `release()`, etc.) work immediately after you commit your lock file. When a GitHub webhook arrives, the orchestrator fetches your lock file and evaluates triggers on the spot -- no advance setup needed.
|
|
1083
|
-
|
|
1084
|
-
Event-based triggers work differently. The orchestrator needs to know about them _before_ the event arrives. This is because event-based triggers are matched against a pre-built registration index rather than being evaluated per-event from a lock file fetch. The six event-based trigger types are:
|
|
1085
|
-
|
|
1086
|
-
- `kiciEvent()` -- custom events emitted from workflow steps
|
|
1087
|
-
- `workflowComplete()` -- fires when a workflow finishes
|
|
1088
|
-
- `jobComplete()` -- fires when a specific job finishes
|
|
1089
|
-
- `genericWebhook()` -- HTTP webhooks from external services
|
|
1090
|
-
- `schedule()` -- cron-based time triggers
|
|
1091
|
-
- `lifecycle()` -- orchestrator lifecycle events (workflow completion, job failure, registration updates)
|
|
1092
|
-
|
|
1093
|
-
All six require the **registration model** to function -- covered in detail below.
|
|
1094
|
-
|
|
1095
|
-
## Event types
|
|
1096
|
-
|
|
1097
|
-
### Custom events
|
|
1098
|
-
|
|
1099
|
-
Custom events are user-defined events emitted from workflow steps using `ctx.emit()`. Use `kiciEvent()` to listen for them.
|
|
1100
|
-
|
|
1101
|
-
```typescript
|
|
1102
|
-
import { kiciEvent } from '@kici-dev/sdk';
|
|
1103
|
-
|
|
1104
|
-
// Listen for a custom event by name
|
|
1105
|
-
kiciEvent({ name: 'deploy-complete' });
|
|
1106
|
-
|
|
1107
|
-
// With payload matching (JSONPath)
|
|
1108
|
-
kiciEvent({ name: 'deploy-complete', match: { '$.env': 'prod' } });
|
|
1109
|
-
|
|
1110
|
-
// With negative filter
|
|
1111
|
-
kiciEvent({ name: 'deploy-complete', not: { '$.env': 'staging' } });
|
|
1112
|
-
|
|
1113
|
-
// From a specific repository
|
|
1114
|
-
kiciEvent({ name: 'deploy-complete', source: 'org/infra-repo' });
|
|
1115
|
-
```
|
|
1116
|
-
|
|
1117
|
-
**Config options:** `name` (required), `match`, `not`, `source`, `description`.
|
|
1118
|
-
|
|
1119
|
-
### System events
|
|
1120
|
-
|
|
1121
|
-
The orchestrator automatically emits completion events when workflows and jobs finish. No manual emission needed -- these fire automatically.
|
|
1122
|
-
|
|
1123
|
-
**Workflow completion:**
|
|
1124
|
-
|
|
1125
|
-
```typescript
|
|
1126
|
-
import { workflowComplete } from '@kici-dev/sdk';
|
|
1127
|
-
|
|
1128
|
-
// Any workflow completion
|
|
1129
|
-
workflowComplete();
|
|
1130
|
-
|
|
1131
|
-
// Specific workflow by name
|
|
1132
|
-
workflowComplete({ name: 'build' });
|
|
1133
|
-
|
|
1134
|
-
// Only successful completions
|
|
1135
|
-
workflowComplete({ name: 'build', status: ['success'] });
|
|
1136
|
-
```
|
|
1137
|
-
|
|
1138
|
-
**Config options:** `name`, `status` (`'success'`, `'failed'`, `'cancelled'`), `source`, `description`.
|
|
1139
|
-
|
|
1140
|
-
**Job completion:**
|
|
1141
|
-
|
|
1142
|
-
```typescript
|
|
1143
|
-
import { jobComplete } from '@kici-dev/sdk';
|
|
1144
|
-
|
|
1145
|
-
// Any job completion
|
|
1146
|
-
jobComplete();
|
|
1147
|
-
|
|
1148
|
-
// Specific workflow + job
|
|
1149
|
-
jobComplete({ workflow: 'build', job: 'test' });
|
|
1150
|
-
|
|
1151
|
-
// Only failures
|
|
1152
|
-
jobComplete({ workflow: 'build', job: 'test', status: ['failed'] });
|
|
1153
|
-
```
|
|
1154
|
-
|
|
1155
|
-
**Config options:** `workflow`, `job`, `status` (`'success'`, `'failed'`, `'cancelled'`, `'skipped'`), `source`, `description`.
|
|
1156
|
-
|
|
1157
|
-
### External events
|
|
1158
|
-
|
|
1159
|
-
Generic webhooks let you trigger workflows from any HTTP service -- Stripe, ArgoCD, Slack, Grafana, or your own internal services.
|
|
1160
|
-
|
|
1161
|
-
```typescript
|
|
1162
|
-
import { genericWebhook } from '@kici-dev/sdk';
|
|
1163
|
-
|
|
1164
|
-
// Match any event from a source
|
|
1165
|
-
genericWebhook({ source: 'stripe' });
|
|
1166
|
-
|
|
1167
|
-
// Match specific event types
|
|
1168
|
-
genericWebhook({ source: 'stripe', events: ['invoice.paid'] });
|
|
1169
|
-
|
|
1170
|
-
// With HMAC-SHA256 signature verification
|
|
1171
|
-
genericWebhook({
|
|
1172
|
-
source: 'stripe',
|
|
1173
|
-
events: ['invoice.paid'],
|
|
1174
|
-
auth: {
|
|
1175
|
-
method: 'hmac-sha256',
|
|
1176
|
-
secret: 'stripe-signing-key',
|
|
1177
|
-
signatureHeader: 'stripe-signature',
|
|
1178
|
-
},
|
|
1179
|
-
});
|
|
1180
|
-
|
|
1181
|
-
// With API key auth
|
|
1182
|
-
genericWebhook({
|
|
1183
|
-
source: 'slack',
|
|
1184
|
-
auth: { method: 'api-key', secret: 'slack-token' },
|
|
1185
|
-
});
|
|
1186
|
-
```
|
|
1187
|
-
|
|
1188
|
-
**Config options:** `source` (required), `events`, `match`, `not`, `auth`, `path`, `description`.
|
|
1189
|
-
|
|
1190
|
-
The `source` field MUST match the `--name` that an operator passed to `kici-admin source add generic --name <name>` when the source was created — that string is the source's identifier in the orchestrator. Generic webhook sources must be created by an operator before events can be received; see [Operator guide: event routing](https://docs.kici.dev/operator/event-routing/) for setup instructions.
|
|
1191
|
-
|
|
1192
|
-
### Schedule events
|
|
1193
|
-
|
|
1194
|
-
Cron-based triggers evaluated by the orchestrator on a periodic interval. Only the Raft leader evaluates schedules in a clustered deployment.
|
|
1195
|
-
|
|
1196
|
-
```typescript
|
|
1197
|
-
import { schedule } from '@kici-dev/sdk';
|
|
1198
|
-
|
|
1199
|
-
// Run every hour
|
|
1200
|
-
schedule({ cron: '0 * * * *' });
|
|
1201
|
-
|
|
1202
|
-
// Run daily at 2 AM UTC
|
|
1203
|
-
schedule({ cron: '0 2 * * *' });
|
|
1204
|
-
|
|
1205
|
-
// Run weekly on Mondays at 9 AM Eastern
|
|
1206
|
-
schedule({ cron: '0 9 * * 1', timezone: 'America/New_York' });
|
|
1207
|
-
```
|
|
1208
|
-
|
|
1209
|
-
**Config options:** `cron` (required), `timezone` (defaults to `'UTC'`), `description`.
|
|
1210
|
-
|
|
1211
|
-
### Lifecycle events
|
|
1212
|
-
|
|
1213
|
-
Lifecycle triggers listen for orchestrator-level events related to workflow execution and system state changes.
|
|
1214
|
-
|
|
1215
|
-
```typescript
|
|
1216
|
-
import { lifecycle } from '@kici-dev/sdk';
|
|
1217
|
-
|
|
1218
|
-
// Trigger when any workflow completes
|
|
1219
|
-
lifecycle({ events: ['workflow_complete'] });
|
|
1220
|
-
|
|
1221
|
-
// Trigger on job failures from a specific repo
|
|
1222
|
-
lifecycle({ events: ['job_failed'], sources: ['org/deploy-repo'] });
|
|
1223
|
-
|
|
1224
|
-
// Trigger when registrations are updated
|
|
1225
|
-
lifecycle({ events: ['registration_updated'] });
|
|
1226
|
-
```
|
|
1227
|
-
|
|
1228
|
-
**Available events:** `'workflow_complete'`, `'job_complete'`, `'job_failed'`, `'registration_updated'`.
|
|
1229
|
-
|
|
1230
|
-
**Config options:** `events` (required), `sources`, `description`.
|
|
1231
|
-
|
|
1232
|
-
## The registration model
|
|
1233
|
-
|
|
1234
|
-
This is the most important concept for understanding event-based triggers.
|
|
1235
|
-
|
|
1236
|
-
### Why registrations exist
|
|
1237
|
-
|
|
1238
|
-
When a GitHub webhook arrives (push, PR, etc.), the orchestrator fetches your lock file from the repository and evaluates triggers on the spot. This works because the event itself tells the orchestrator _which repository_ to look at.
|
|
1239
|
-
|
|
1240
|
-
Event-based triggers are different. When a cron timer fires or a custom event is emitted, there is no incoming webhook pointing to a specific repository. The orchestrator needs to know _in advance_ which workflows care about which events. That is what the registration model provides: a pre-built index of event-based workflows.
|
|
1241
|
-
|
|
1242
|
-
### How registration works
|
|
1243
|
-
|
|
1244
|
-
1. You define a workflow with an event-based trigger (e.g., `schedule()`, `kiciEvent()`, `genericWebhook()`)
|
|
1245
|
-
2. You compile the workflow (`kici compile`), which produces a lock file
|
|
1246
|
-
3. You push the lock file to your repository's **default branch** (e.g., `main` or `master`)
|
|
1247
|
-
4. The orchestrator receives the push webhook, detects it targets the default branch, and extracts all workflows with event-based triggers from the lock file
|
|
1248
|
-
5. Those workflows are stored in the orchestrator's registration database
|
|
1249
|
-
6. From that point on, matching events will trigger those workflows
|
|
1250
|
-
|
|
1251
|
-
### Key implications
|
|
1252
|
-
|
|
1253
|
-
- **Event-based workflows do not trigger until you push to the default branch.** If you add a new `schedule()` workflow, it will not start running until you merge to your default branch. This is by design -- the orchestrator cannot match events to workflows it does not know about.
|
|
1254
|
-
|
|
1255
|
-
- **Registration is automatic.** There is no manual setup. Push your code, and the orchestrator handles the rest.
|
|
1256
|
-
|
|
1257
|
-
- **Registrations refresh on every default-branch push.** If you add, remove, or modify event-based workflows and push to the default branch, the orchestrator updates its registration index automatically. Removed workflows stop triggering. New workflows start triggering.
|
|
1258
|
-
|
|
1259
|
-
- **Git-based triggers are unaffected.** Triggers like `push()`, `pr()`, and `tag()` do not use registrations. They work immediately from any branch because the orchestrator evaluates them per-event from the lock file.
|
|
1260
|
-
|
|
1261
|
-
### Practical example
|
|
1262
|
-
|
|
1263
|
-
You create a nightly build workflow:
|
|
1264
|
-
|
|
1265
|
-
```typescript
|
|
1266
|
-
import { workflow, job, step, schedule } from '@kici-dev/sdk';
|
|
1267
|
-
|
|
1268
|
-
export default workflow('nightly-build', {
|
|
1269
|
-
on: schedule({ cron: '0 2 * * *' }),
|
|
1270
|
-
jobs: [
|
|
1271
|
-
job('build', {
|
|
1272
|
-
runsOn: 'linux',
|
|
1273
|
-
steps: [
|
|
1274
|
-
step('build', async ({ $ }) => {
|
|
1275
|
-
await $`pnpm build`;
|
|
1276
|
-
}),
|
|
1277
|
-
],
|
|
1278
|
-
}),
|
|
1279
|
-
],
|
|
1280
|
-
});
|
|
1281
|
-
```
|
|
1282
|
-
|
|
1283
|
-
You compile it, commit the lock file, and push to a feature branch. **Nothing happens** -- the cron will not fire because the orchestrator has not registered this workflow yet.
|
|
1284
|
-
|
|
1285
|
-
You merge the feature branch into `main`. On the merge push, the orchestrator extracts the `nightly-build` workflow (it has a `ScheduleTrigger`) and registers it. Starting at the next 2 AM UTC, the workflow will trigger.
|
|
1286
|
-
|
|
1287
|
-
## How events are matched
|
|
1288
|
-
|
|
1289
|
-
When an event arrives, the orchestrator follows this flow:
|
|
1290
|
-
|
|
1291
|
-
1. **Event received** -- a custom event is emitted by a step, a cron timer fires, or a generic webhook arrives
|
|
1292
|
-
2. **Registration lookup** -- the orchestrator queries its registration index for workflows matching the event type (e.g., all workflows with `ScheduleTrigger` for a cron fire, or all workflows with `KiciEventTrigger` for a custom event)
|
|
1293
|
-
3. **Trigger evaluation** -- for each candidate workflow, the orchestrator evaluates the trigger conditions: event name patterns, payload matching, status filters, source filters
|
|
1294
|
-
4. **Dispatch** -- matched workflows are dispatched to agents for execution, following the same job queue and agent routing as git-triggered workflows
|
|
1295
|
-
|
|
1296
|
-
This lookup is fast because the registration index is held in memory and refreshed only when the registry version changes (on default-branch pushes).
|
|
1297
|
-
|
|
1298
|
-
### Cross-source webhook delivery
|
|
1299
|
-
|
|
1300
|
-
The catch-all `webhook()` trigger (see [SDK reference: webhook()](https://docs.kici.dev/user/sdk/triggers/#webhook)) participates in this same registration lookup, but with one twist: it fires for matching events arriving via **any** inbound webhook source in the same org, not just the source the workflow's repo is bound to. The orchestrator maintains a `(customerId, eventName)` index over webhook trigger registrations and consults it on every inbound generic webhook.
|
|
1301
|
-
|
|
1302
|
-
The lookup is structurally org-isolated — a generic webhook delivered to org A can never reach a workflow registered against org B, because foreign-org rows live in a different bucket of the index. When a webhook fires across sources, the runtime clone token, repo URL, and check-status posting all come from the **registration's** source bundle, not the inbound source. The inbound source contributes only the event payload.
|
|
1303
|
-
|
|
1304
|
-
## Circuit breaker
|
|
1305
|
-
|
|
1306
|
-
Events can trigger workflows that emit more events, creating chains. The circuit breaker prevents runaway event storms.
|
|
1307
|
-
|
|
1308
|
-
### Chain depth limit
|
|
1309
|
-
|
|
1310
|
-
Each event carries a `chainDepth` counter. When a workflow triggered by an event emits a new event, the new event's chain depth increments. The orchestrator rejects events that exceed the maximum chain depth.
|
|
1311
|
-
|
|
1312
|
-
- **Default limit:** 10 levels deep
|
|
1313
|
-
- **What happens when hit:** the event is dropped and logged. It is not queued for later delivery.
|
|
1314
|
-
|
|
1315
|
-
For example: Workflow A emits event X (depth 0) -> Workflow B triggers, emits event Y (depth 1) -> ... -> at depth 10, any further emitted events are dropped.
|
|
1316
|
-
|
|
1317
|
-
### Rate limiting
|
|
1318
|
-
|
|
1319
|
-
Each workflow is rate-limited on how many events it can process per minute, using a sliding window.
|
|
1320
|
-
|
|
1321
|
-
- **Default limit:** 100 events per workflow per minute
|
|
1322
|
-
- **What happens when hit:** additional events for that workflow are dropped and logged until the window clears.
|
|
1323
|
-
|
|
1324
|
-
These defaults are hardcoded in the orchestrator and are not currently configurable via environment variables.
|
|
1325
|
-
|
|
1326
|
-
## Delivery guarantees
|
|
1327
|
-
|
|
1328
|
-
KiCI's event router delivers every accepted event with **at-least-once** semantics:
|
|
1329
|
-
|
|
1330
|
-
- An event that passes the circuit breaker (chain depth + rate limit) and commits
|
|
1331
|
-
to the `kici_events` table is guaranteed to dispatch to all matching workflows
|
|
1332
|
-
at least once.
|
|
1333
|
-
- Each dispatch attempt acquires a short-lived lease (default 60 s) on the row.
|
|
1334
|
-
If the dispatching node crashes or the handler throws, the lease expires (or
|
|
1335
|
-
is released on failure) and the event is automatically retried.
|
|
1336
|
-
- The retry policy is exponential backoff with full jitter: base 5 s, cap 5 min,
|
|
1337
|
-
up to 5 attempts before the event lands in the **DLQ** (dead-letter queue).
|
|
1338
|
-
Operators triage DLQ entries via `kici-admin event-dlq list / count / retry / discard`.
|
|
1339
|
-
|
|
1340
|
-
**What this means for workflow authors:**
|
|
1341
|
-
|
|
1342
|
-
- **Make event handlers idempotent.** A retried dispatch may run a handler more
|
|
1343
|
-
than once (e.g. if the first attempt threw after a partial side-effect).
|
|
1344
|
-
Workflows that mutate external state should use idempotency keys, conditional
|
|
1345
|
-
writes, or other deduplication patterns — same advice as for any distributed
|
|
1346
|
-
CI system.
|
|
1347
|
-
- **Schedule fires are at-least-once too.** A cron schedule that fires while a
|
|
1348
|
-
leader is being killed will commit (atomically with `cron_last_fired`) or roll
|
|
1349
|
-
back together — never half. Recovery on the new leader does not backfill
|
|
1350
|
-
multiple missed instants; if your workflow needs at-least-N guarantees across
|
|
1351
|
-
outages, drive it from a different mechanism (e.g. a workflow that runs more
|
|
1352
|
-
frequently and emits its own custom event).
|
|
1353
|
-
- **Drops are still possible — and visible.** Events rejected by the circuit
|
|
1354
|
-
breaker (chain depth or rate limit exceeded) are dropped and logged, not
|
|
1355
|
-
retried. That's a deliberate safety mechanism; the metric to watch is
|
|
1356
|
-
`kici_orch_events_dropped_total{reason}`.
|
|
1357
|
-
|
|
1358
|
-
## Emitting custom events
|
|
1359
|
-
|
|
1360
|
-
Custom events are emitted from workflow steps using `ctx.emit()`. You can optionally define typed event schemas using `defineEvent()`.
|
|
1361
|
-
|
|
1362
|
-
### Basic emission
|
|
1363
|
-
|
|
1364
|
-
```typescript
|
|
1365
|
-
import { workflow, job, step, push } from '@kici-dev/sdk';
|
|
1366
|
-
|
|
1367
|
-
export default workflow('build', {
|
|
1368
|
-
on: push({ branches: 'main' }),
|
|
1369
|
-
jobs: [
|
|
1370
|
-
job('build', {
|
|
1371
|
-
runsOn: 'linux',
|
|
1372
|
-
steps: [
|
|
1373
|
-
step('build', async ({ $ }) => {
|
|
1374
|
-
await $`pnpm build`;
|
|
1375
|
-
}),
|
|
1376
|
-
step('notify', async (ctx) => {
|
|
1377
|
-
await ctx.emit('build-complete', {
|
|
1378
|
-
version: '1.0.0',
|
|
1379
|
-
success: true,
|
|
1380
|
-
});
|
|
1381
|
-
}),
|
|
1382
|
-
],
|
|
1383
|
-
}),
|
|
1384
|
-
],
|
|
1385
|
-
});
|
|
1386
|
-
```
|
|
1387
|
-
|
|
1388
|
-
### Typed event definitions
|
|
1389
|
-
|
|
1390
|
-
Use `defineEvent()` with Zod schemas to create a typed contract for event payloads:
|
|
1391
|
-
|
|
1392
|
-
```typescript
|
|
1393
|
-
import { defineEvent, z } from '@kici-dev/sdk';
|
|
1394
|
-
|
|
1395
|
-
export const deployComplete = defineEvent(
|
|
1396
|
-
'deploy-complete',
|
|
1397
|
-
z.object({
|
|
1398
|
-
env: z.string(),
|
|
1399
|
-
version: z.string(),
|
|
1400
|
-
services: z.array(z.string()),
|
|
1401
|
-
}),
|
|
1402
|
-
);
|
|
1403
|
-
```
|
|
1404
|
-
|
|
1405
|
-
Then emit using the definition — the payload is checked against the schema:
|
|
1406
|
-
|
|
1407
|
-
```typescript
|
|
1408
|
-
step('emit', async (ctx) => {
|
|
1409
|
-
await ctx.emit(deployComplete, {
|
|
1410
|
-
env: 'prod',
|
|
1411
|
-
version: '1.2.3',
|
|
1412
|
-
services: ['api', 'web'],
|
|
1413
|
-
});
|
|
1414
|
-
});
|
|
1415
|
-
```
|
|
1416
|
-
|
|
1417
|
-
And consume in another workflow:
|
|
1418
|
-
|
|
1419
|
-
```typescript
|
|
1420
|
-
import { workflow, job, step, kiciEvent } from '@kici-dev/sdk';
|
|
1421
|
-
|
|
1422
|
-
export default workflow('post-deploy', {
|
|
1423
|
-
on: kiciEvent({ name: 'deploy-complete', match: { '$.env': 'prod' } }),
|
|
1424
|
-
jobs: [
|
|
1425
|
-
job('smoke-test', {
|
|
1426
|
-
runsOn: 'linux',
|
|
1427
|
-
steps: [
|
|
1428
|
-
step('test', async ({ $ }) => {
|
|
1429
|
-
await $`./scripts/smoke-test.sh`;
|
|
1430
|
-
}),
|
|
1431
|
-
],
|
|
1432
|
-
}),
|
|
1433
|
-
],
|
|
1434
|
-
});
|
|
1435
|
-
```
|
|
1436
|
-
|
|
1437
|
-
Custom events are delivered immediately when emitted (mid-workflow, not queued until workflow completion). See the [SDK reference: emitting events](https://docs.kici.dev/user/sdk/validation-events/#emitting-events) section for the full `ctx.emit()` API.
|
|
1438
|
-
|
|
1439
|
-
## See also
|
|
1440
|
-
|
|
1441
|
-
- [SDK reference: event triggers](https://docs.kici.dev/user/sdk/triggers/#event-triggers) -- complete API signatures for all trigger builders
|
|
1442
|
-
- [SDK reference: emitting events](https://docs.kici.dev/user/sdk/validation-events/#emitting-events) -- `ctx.emit()` and `defineEvent()` API
|
|
1443
|
-
- [Workflow patterns: workflow chaining](https://docs.kici.dev/user/patterns/integrations/#workflow-chaining) -- examples of event-driven workflow chains
|
|
1444
|
-
- [Operator guide: event routing](https://docs.kici.dev/operator/event-routing/) -- configuring generic webhook sources, trust relationships, and event routing
|
|
1445
|
-
- [Architecture: event system](https://docs.kici.dev/architecture/webhooks/event-system/) -- internal event routing design, registration model, cluster synchronization
|
|
1446
|
-
|
|
1447
|
-
---
|
|
1448
|
-
|
|
1449
|
-
## Global workflows
|
|
1450
|
-
|
|
1451
|
-
Source: https://docs.kici.dev/user/global-workflows/
|
|
1452
|
-
|
|
1453
|
-
Global workflows let one **workflow repo** define jobs that run on events from many **source repos** in the same org. They're the answer to "I want one CI policy / release pipeline / security scan to fire on every repo without copy-pasting `.kici/` folders everywhere."
|
|
1454
|
-
|
|
1455
|
-
If you've only ever used per-repo workflows so far, start with the mental model section — global workflows add two new concepts (workflow repo vs. source repo, and authoring vs. source axes) that show up everywhere from SDK syntax to dashboard settings.
|
|
1456
|
-
|
|
1457
|
-
## Mental model
|
|
1458
|
-
|
|
1459
|
-
| Term | Meaning |
|
|
1460
|
-
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1461
|
-
| Workflow repo | The repo whose `.kici/workflows/*.ts` file **declares** the global workflow. Holds the steps. Also known as the _authoring_ repo. |
|
|
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. |
|
|
1463
|
-
| Global | A workflow whose trigger carries one or more `repos:` glob patterns. The presence of `repos:` is what classifies a workflow as global. |
|
|
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. |
|
|
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_. |
|
|
1466
|
-
|
|
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.
|
|
1468
|
-
|
|
1469
|
-
## Declaring a global workflow
|
|
1470
|
-
|
|
1471
|
-
Add `repos:` to any trigger. Any workflow with at least one `repos:`-bearing trigger becomes global automatically; no separate flag is required.
|
|
1472
|
-
|
|
1473
|
-
```ts
|
|
1474
|
-
import { workflow, job, step, push } from '@kici-dev/sdk';
|
|
1475
|
-
|
|
1476
|
-
export default workflow('org-lint', {
|
|
1477
|
-
on: [
|
|
1478
|
-
push({
|
|
1479
|
-
repos: ['myorg/*', '!myorg/archived-*'],
|
|
1480
|
-
branches: ['main'],
|
|
1481
|
-
}),
|
|
1482
|
-
],
|
|
1483
|
-
jobs: [
|
|
1484
|
-
job('lint', {
|
|
1485
|
-
steps: [
|
|
1486
|
-
step('lint-all', async ({ $, env }) => {
|
|
1487
|
-
await $`echo source=${env.KICI_SOURCE_REPO_PATH ?? 'unknown'}`;
|
|
1488
|
-
await $`npm run lint`;
|
|
1489
|
-
}),
|
|
1490
|
-
],
|
|
1491
|
-
}),
|
|
1492
|
-
],
|
|
1493
|
-
});
|
|
1494
|
-
```
|
|
1495
|
-
|
|
1496
|
-
Patterns in `repos:` use the same globbing as `branches:` / `paths:` — plain globs (`myorg/*`), a leading `!` for exclusions (`!myorg/fork-*`), and a fully-qualified `owner/repo` identity for exact matches (`myorg/platform`). A bare `**` matches every repo in the org.
|
|
1497
|
-
|
|
1498
|
-
### At a dual-repo checkout
|
|
1499
|
-
|
|
1500
|
-
The agent receives two sets of context during a global workflow execution:
|
|
1501
|
-
|
|
1502
|
-
| `env` var | Points to |
|
|
1503
|
-
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1504
|
-
| `KICI_SOURCE_REPO_PATH` | The **source** repo's working tree (the repo that emitted the event). This is the repo the job's `$` / `git` commands operate on by default. |
|
|
1505
|
-
| `KICI_WORKFLOW_REPO_PATH` | The **workflow** repo's working tree (the repo that authored the workflow). Useful for reading shared scripts or config from your CI repo. |
|
|
1506
|
-
|
|
1507
|
-
Source repo secrets are **not** available to a global workflow's job by default — see _Elevated access_ below.
|
|
1508
|
-
|
|
1509
|
-
## Enabling global workflows
|
|
1510
|
-
|
|
1511
|
-
Global workflows are **opt-in per org**. In a fresh org, `repos:`-bearing workflows are registered but never dispatched.
|
|
1512
|
-
|
|
1513
|
-
1. Open the dashboard → **Settings → Global workflows**.
|
|
1514
|
-
2. Turn on **Enable global workflows** (the master toggle). This is the kill-switch — every other toggle below is ignored while this is off.
|
|
1515
|
-
3. Decide which authoring/source controls you need:
|
|
1516
|
-
|
|
1517
|
-
| Setting | What it controls | Typical use |
|
|
1518
|
-
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
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. |
|
|
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-*`. |
|
|
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. |
|
|
1522
|
-
|
|
1523
|
-
All three lists accept globs. Leading `!` inside a single pattern is not supported here; negation is via the list-is-implicit-deny semantics, so keep it simple (`myorg/ci-*`, `myorg/platform-*`).
|
|
1524
|
-
|
|
1525
|
-
### Saving and reverting
|
|
1526
|
-
|
|
1527
|
-
The page is a two-state editor — changes are local until you click **Save changes**, and you can abandon them with **Discard changes**. There is no partial save; the PATCH is all-or-nothing per save click.
|
|
1528
|
-
|
|
1529
|
-
## Security model
|
|
1530
|
-
|
|
1531
|
-
### Two independent axes
|
|
1532
|
-
|
|
1533
|
-
A global workflow fires only if:
|
|
1534
|
-
|
|
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:
|
|
1536
|
-
- At registration time (extraction from the lock file — non-matching globals are dropped with a warning).
|
|
1537
|
-
- At dispatch time (defense-in-depth — policy changes after registration still take effect).
|
|
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.
|
|
1539
|
-
|
|
1540
|
-
Both checks are logged to the orchestrator. Grep the logs for `Skipping global workflow` to see enforcement in action.
|
|
1541
|
-
|
|
1542
|
-
### Elevated access (source-repo secrets)
|
|
1543
|
-
|
|
1544
|
-
By default a global workflow's job runs with credentials scoped to the **workflow** repo — it can clone both repos but cannot read the source repo's scoped secrets. That's the safe default: a random workflow in `myorg/ci-pipelines` does not get read access to secrets in `myorg/backend` just because it runs on a push there.
|
|
1545
|
-
|
|
1546
|
-
Adding the authoring repo to the _Elevated access_ list flips that: the job receives the source repo's secret context, so deploy and release flows that need `NPM_TOKEN` / `AWS_ROLE_ARN` / etc. from the source repo can read them. Treat elevated repos as effective owners of every source repo's CI secrets — only add repos you fully trust.
|
|
1547
|
-
|
|
1548
|
-
## When does it fire?
|
|
1549
|
-
|
|
1550
|
-
Same-repo globals (a workflow in `myorg/app` with `repos: ['myorg/app']`) fire on pushes to `myorg/app`. Cross-repo globals fire on pushes to any source repo whose identifier matches a glob on the authoring workflow's trigger. The orchestrator de-duplicates between the per-repo and cross-repo matching passes, so a single event produces at most one run per (workflow, source-repo, trigger) triple.
|
|
1551
|
-
|
|
1552
|
-
Non-push triggers work too — `pr()`, `tag()`, `comment()`, `release()`, `workflowRun()`, etc. all accept `repos:`. `kiciEvent()` / `schedule()` / cron-like triggers have no source repo, so they're always per-org-registered regardless of `repos:`.
|
|
1553
|
-
|
|
1554
|
-
## Troubleshooting
|
|
1555
|
-
|
|
1556
|
-
| Symptom | Likely cause | Where to look |
|
|
1557
|
-
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
1558
|
-
| Global workflow registered but never runs | Master toggle OFF, or allow-list blocks the authoring repo, or deny-list blocks the source repo | Orchestrator log: `Skipping global workflow dispatch` / `Skipping global workflow registration: not permitted` |
|
|
1559
|
-
| `repos:` has no effect — workflow only fires on its own repo | Master toggle OFF. Without opt-in, the orchestrator treats the workflow as per-repo-only. | Dashboard → Settings → Global workflows (top toggle) |
|
|
1560
|
-
| Source repo secrets unavailable in a global job | Expected default — elevate the authoring repo to grant access. | Dashboard → Settings → Global workflows → _Elevated access_ |
|
|
1561
|
-
| Dashboard shows workflow twice after registering | Both a generic webhook source and a provider source (github, generic) re-registered the same repo. | Check `workflow_registrations` via `kici-admin workflow list` and confirm the right routing key owns the workflow. |
|
|
1562
|
-
|
|
1563
|
-
## See also
|
|
1564
|
-
|
|
1565
|
-
- [Architecture — global workflows](https://docs.kici.dev/architecture/global-workflows/) — dual-query dispatch flow, cross-provider auth, security model, lock-file schema.
|
|
1566
|
-
- [Universal-git provider](https://docs.kici.dev/user/providers/universal-git/#global-workflows) — how global workflows interact with `generic:<orgId>:<sourceId>` routing keys.
|
|
1567
|
-
- [SDK reference](https://docs.kici.dev/user/sdk-reference/) — the full set of triggers that accept `repos:`.
|
|
1568
|
-
|
|
1569
|
-
---
|
|
1570
|
-
|
|
1571
|
-
## Idempotent steps and check mode
|
|
1572
|
-
|
|
1573
|
-
Source: https://docs.kici.dev/user/idempotent-steps/
|
|
1574
|
-
|
|
1575
|
-
An **idempotent step** describes _desired state_ rather than a fixed sequence of
|
|
1576
|
-
commands. You give the step a `check` function that inspects the world and a
|
|
1577
|
-
`run` function that converges it. KiCI then executes the workflow in one of two
|
|
1578
|
-
modes:
|
|
1579
|
-
|
|
1580
|
-
- **Apply mode** (the default): for each step, `check()` runs first; on drift the
|
|
1581
|
-
step applies the change; when already in sync the step is skipped.
|
|
1582
|
-
- **Check mode** (`--check`): for each step, `check()` runs and KiCI reports what
|
|
1583
|
-
_would_ change — **without changing anything**. This is the same model as a
|
|
1584
|
-
dry-run plan: you see the drift before any side effect happens.
|
|
1585
|
-
|
|
1586
|
-
This turns a workflow into convergent configuration management: re-running an
|
|
1587
|
-
apply is safe (in-sync steps do nothing), and a check-mode run is a read-only
|
|
1588
|
-
preview you can gate a build on.
|
|
1589
|
-
|
|
1590
|
-
## Authoring a checked step
|
|
1591
|
-
|
|
1592
|
-
Add a `check` facet to the existing `step()` factory. When `check` is present,
|
|
1593
|
-
`run` becomes the _apply_ function and receives the drift value `check`
|
|
1594
|
-
returned:
|
|
1595
|
-
|
|
1596
|
-
```typescript
|
|
1597
|
-
import { step, z } from '@kici-dev/sdk';
|
|
1598
|
-
|
|
1599
|
-
const configureNginx = step('configure-nginx', {
|
|
1600
|
-
// optional schema for the drift value — gives the dashboard a typed shape
|
|
1601
|
-
drift: z.object({ want: z.string() }),
|
|
1602
|
-
|
|
1603
|
-
// read-only inspection; return null when already in the desired state
|
|
1604
|
-
check: async (ctx) => {
|
|
1605
|
-
const current = await ctx.$`nginx -T`;
|
|
1606
|
-
return current.stdout.includes(DESIRED) ? null : { want: DESIRED };
|
|
1607
|
-
},
|
|
1608
|
-
|
|
1609
|
-
// human-readable preview line — REQUIRED when check is set. It is the drift's
|
|
1610
|
-
// serializable face: it streams to the logs and persists for the dashboard.
|
|
1611
|
-
summarize: (drift) => `would rewrite nginx.conf (${drift.want.length} bytes)`,
|
|
1612
|
-
|
|
1613
|
-
// apply — runs only when check returned drift (apply mode); receives that drift
|
|
1614
|
-
run: async (ctx, drift) => {
|
|
1615
|
-
await writeConfig(drift.want);
|
|
1616
|
-
return { reloaded: true };
|
|
1617
|
-
},
|
|
1618
|
-
|
|
1619
|
-
// optional — runs when check returned null, to produce the step's outputs
|
|
1620
|
-
whenInSync: async () => ({ reloaded: false }),
|
|
1621
|
-
});
|
|
1622
|
-
```
|
|
1623
|
-
|
|
1624
|
-
### The facet fields
|
|
1625
|
-
|
|
1626
|
-
| Field | Required | Purpose |
|
|
1627
|
-
| ------------ | ---------------- | ---------------------------------------------------------------------- |
|
|
1628
|
-
| `check` | to opt in | Read-only inspection. Return a drift value, or `null` when in sync. |
|
|
1629
|
-
| `summarize` | when `check` set | Human-readable, serializable preview of the drift. Streams + persists. |
|
|
1630
|
-
| `run` | always | Apply function. With `check`, it receives the drift as its second arg. |
|
|
1631
|
-
| `whenInSync` | optional | Produces the step's outputs when `check` returned `null`. |
|
|
1632
|
-
| `drift` | optional | Schema that validates / shapes the drift value. |
|
|
1633
|
-
|
|
1634
|
-
`summarize` is **required** whenever `check` is declared. `run` and `whenInSync`
|
|
1635
|
-
both produce the same output type — one output shape per step, whichever path
|
|
1636
|
-
runs. Every other step facet (`cache`, `rules`, `continueOnError`, `timeout`,
|
|
1637
|
-
`retry`, `approval`, `onCancel`, `cleanup`, `outputs`) composes unchanged.
|
|
1638
|
-
|
|
1639
|
-
A plain `step()` without `check` keeps its exact current behavior — the check
|
|
1640
|
-
facet is fully optional.
|
|
1641
|
-
|
|
1642
|
-
## Run modes
|
|
1643
|
-
|
|
1644
|
-
A run carries one of three modes:
|
|
1645
|
-
|
|
1646
|
-
| Mode | CLI flags | Behavior |
|
|
1647
|
-
| --------------------- | ------------------------- | ------------------------------------------------------------------------------------------ |
|
|
1648
|
-
| `apply` | (default, no flags) | Converge: drift ⇒ apply ⇒ **applied**; null ⇒ **in sync** (skipped). |
|
|
1649
|
-
| `check` | `--check` | Preview only: drift ⇒ **would change**; null ⇒ **in sync**. Never applies. Always exits 0. |
|
|
1650
|
-
| `check-fail-on-drift` | `--check --fail-on-drift` | Same as check, but the run **fails** if any step reports drift. |
|
|
1651
|
-
|
|
1652
|
-
Per-step outcomes:
|
|
1653
|
-
|
|
1654
|
-
- **applied** — drift was found and the step applied the change (apply mode).
|
|
1655
|
-
- **in sync** — `check` returned `null`; nothing to do.
|
|
1656
|
-
- **would change** — drift was found in check mode; the change was previewed, not applied.
|
|
1657
|
-
- **no check** — a plain step (no `check`) reached under check mode. A
|
|
1658
|
-
side-effecting step can't be safely previewed, so it is skipped.
|
|
1659
|
-
|
|
1660
|
-
In check mode KiCI never invokes a checked step's `run` (apply) — the preview is
|
|
1661
|
-
guaranteed side-effect-free.
|
|
1662
|
-
|
|
1663
|
-
## Running in check mode
|
|
1664
|
-
|
|
1665
|
-
`--check` and `--fail-on-drift` control drift reporting on `kici run remote`:
|
|
1666
|
-
|
|
1667
|
-
```bash
|
|
1668
|
-
# Apply (default): converge the workflow.
|
|
1669
|
-
kici run push --local
|
|
1670
|
-
kici run remote my-fixture
|
|
1671
|
-
|
|
1672
|
-
# Check: report drift, change nothing. Always exits 0.
|
|
1673
|
-
kici run remote my-fixture --check
|
|
1674
|
-
|
|
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
|
|
1678
|
-
```
|
|
1679
|
-
|
|
1680
|
-
`--fail-on-drift` only modifies check mode — passing it without `--check` is an
|
|
1681
|
-
error.
|
|
1682
|
-
|
|
1683
|
-
## Where outcomes show up
|
|
1684
|
-
|
|
1685
|
-
A check-mode run is labeled in the dashboard with a **CHECK MODE — preview**
|
|
1686
|
-
badge on the run header. Each step shows its outcome chip — applied / in sync /
|
|
1687
|
-
would change / no check — and, when drift was detected, the `summarize` line
|
|
1688
|
-
describing what would change. The rendering is read-only.
|
|
1689
|
-
|
|
1690
|
-
## See also
|
|
1691
|
-
|
|
1692
|
-
- [Idempotent SDK helpers](https://docs.kici.dev/user/sdk/idempotent/) — the `idempotent()` / `idempotentStep()` convenience wrappers (always apply on drift), plus `checkStep()`, the clean-shape sibling that respects the run-level check mode.
|
|
1693
|
-
- [Core SDK reference](https://docs.kici.dev/user/sdk/core/) — the `step()`, `job()`, and `workflow()` factories the check facet extends.
|
|
1694
|
-
- [Lock file and drift](https://docs.kici.dev/user/lock-file-and-drift/) — how the lock file carries step capability flags.
|
|
1695
|
-
|
|
1696
|
-
---
|
|
1697
|
-
|
|
1698
691
|
## Private npm registries
|
|
1699
692
|
|
|
1700
693
|
Source: https://docs.kici.dev/user/private-registries/
|
|
@@ -1744,7 +737,7 @@ export default workflow('build', {
|
|
|
1744
737
|
|
|
1745
738
|
Per-field rules:
|
|
1746
739
|
|
|
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-
|
|
740
|
+
- **`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/orchestrator/kici-admin/org-settings/#allow-http-npm--permit-non-https-private-npm-registries)).
|
|
1748
741
|
- **`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`.
|
|
1749
742
|
- **`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.
|
|
1750
743
|
- **`alwaysAuth`** — Defaults to `true`. Forces npm to send the token on every request (even GETs), which is what most managed-registry providers require.
|
|
@@ -1921,7 +914,7 @@ registries: [
|
|
|
1921
914
|
## Security model
|
|
1922
915
|
|
|
1923
916
|
- **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
|
|
917
|
+
- **Untrusted refs get no tokens.** When the trust resolution returns anything other than `trusted` — every fork pull request does — the orchestrator strips both `npmRegistries` and `installEnvSecrets` out of the dispatch. The install runs without auth and fails naturally on the first private dep. A fork pull request cannot observe a registry token, even if a context lacks an explicit [minimum trust](https://docs.kici.dev/user/contexts/#minimum-trust) rule.
|
|
1925
918
|
- **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.
|
|
1926
919
|
- **Stderr is redacted.** If the install fails, the agent masks every token literal out of the surfaced stderr / stdout chunks before logging.
|
|
1927
920
|
- **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.
|
|
@@ -1958,7 +951,7 @@ The dashboard JSON lives at `infra/terraform/modules/grafana/dashboards/install-
|
|
|
1958
951
|
|
|
1959
952
|
- [Secrets](https://docs.kici.dev/user/secrets/) — how to seed the `<context>:<secret-name>` values referenced by `tokenSecret` / `installEnv`.
|
|
1960
953
|
- [Contexts](https://docs.kici.dev/user/contexts/) — protection rules (`branch_restrictions`, `requires_review`, `minimum_trust`) that the install gate inherits.
|
|
1961
|
-
- [Operator: `kici-admin org-settings`](https://docs.kici.dev/operator/kici-admin-
|
|
954
|
+
- [Operator: `kici-admin org-settings`](https://docs.kici.dev/operator/orchestrator/kici-admin/org-settings/#org-settings----org-level-security-policy) — the `allow_http_npm_registries` toggle and other org-scoped knobs.
|
|
1962
955
|
|
|
1963
956
|
---
|
|
1964
957
|
|
|
@@ -2113,11 +1106,12 @@ commonly, an offline `{ issuer, jwks }` file for air-gapped checks.
|
|
|
2113
1106
|
|
|
2114
1107
|
### Why you supply it out-of-band
|
|
2115
1108
|
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
`iss: https://attacker.example`
|
|
2120
|
-
|
|
1109
|
+
The verifier already resolves a sensible default, so why is naming the trust
|
|
1110
|
+
root a supported step at all -- why not let the verifier read the issuer from
|
|
1111
|
+
the token? Because the issuer named **inside** a token cannot be trusted. A
|
|
1112
|
+
forged bundle could carry a token that names `iss: https://attacker.example`
|
|
1113
|
+
_and_ bundle a key set that "verifies" it. That makes the whole signature chain
|
|
1114
|
+
circular and self-attesting. The verifier
|
|
2121
1115
|
therefore pins to an issuer you supply out-of-band and checks the token against
|
|
2122
1116
|
_that_ — the bundle is verified against a key set you trust, not one it shipped
|
|
2123
1117
|
with. Naming the trust root is a security requirement, not a multiple-choice
|
|
@@ -2264,7 +1258,7 @@ KiCI provides an explicit secrets API that gives workflow steps controlled acces
|
|
|
2264
1258
|
|
|
2265
1259
|
## Overview
|
|
2266
1260
|
|
|
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.
|
|
1261
|
+
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.
|
|
2268
1262
|
|
|
2269
1263
|
This design prevents accidental secret leakage through child processes, log output, or error messages. Only secrets you explicitly request are loaded into memory.
|
|
2270
1264
|
|
|
@@ -2285,13 +1279,13 @@ Use whichever fits the workflow — most small teams stay on the dashboard; ops
|
|
|
2285
1279
|
|
|
2286
1280
|
### When the operator has disabled dashboard writes
|
|
2287
1281
|
|
|
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:
|
|
1282
|
+
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:
|
|
2289
1283
|
|
|
2290
1284
|
- 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.
|
|
2291
1285
|
- The dashboard's secrets page still lists secret **names**, scopes, and bindings — only the value-entry path moves to the CLI.
|
|
2292
1286
|
- `kici-admin secret set` becomes the single entry point for new and updated secret values.
|
|
2293
1287
|
|
|
2294
|
-
This configuration is common for SOC2-prep and regulated workloads, where the customer requirement is "the SaaS control plane process never receives plaintext customer secret values." The dashboard remains usable for everything else (read paths, name CRUD,
|
|
1288
|
+
This configuration is common for SOC2-prep and regulated workloads, where the customer requirement is "the SaaS control plane process never receives plaintext customer secret values." The dashboard remains usable for everything else (read paths, name CRUD, context bindings).
|
|
2295
1289
|
|
|
2296
1290
|
### CLI input modes
|
|
2297
1291
|
|
|
@@ -2326,7 +1320,7 @@ Two cross-cutting flags help every mode:
|
|
|
2326
1320
|
|
|
2327
1321
|
`kici-admin variable set` uses the same flags for non-encrypted variables, plus `--locked` to mark a variable as immutable from subsequent dashboard writes.
|
|
2328
1322
|
|
|
2329
|
-
A full reference of input modes — including the default-mode resolution rules and the security trade-offs of each — lives in [Dashboard-write policy → CLI input modes](https://docs.kici.dev/operator/security/dashboard-write-policy
|
|
1323
|
+
A full reference of input modes — including the default-mode resolution rules and the security trade-offs of each — lives in [Dashboard-write policy → CLI input modes](https://docs.kici.dev/operator/security/dashboard-write-policy/#cli-input-modes-for-the-plaintext-path).
|
|
2330
1324
|
|
|
2331
1325
|
## Accessing secrets
|
|
2332
1326
|
|
|
@@ -2559,8 +1553,10 @@ Note that `get()` is async -- you must `await` the result.
|
|
|
2559
1553
|
|
|
2560
1554
|
## Typed secrets
|
|
2561
1555
|
|
|
2562
|
-
When you run `kici types`, the compiler generates a `.kici/secrets.d.ts` file that provides type-safe autocompletion for your secret keys. The generated types augment the `StepSecrets` interface so that `ctx.secrets.get('...')` and `ctx.secrets.has('...')` offer suggestions for known keys.
|
|
1556
|
+
When you run `kici types`, the compiler generates a `.kici/types/secrets.d.ts` file that provides type-safe autocompletion for your secret keys. The generated types augment the `StepSecrets` interface so that `ctx.secrets.get('...')` and `ctx.secrets.has('...')` offer suggestions for known keys.
|
|
1557
|
+
|
|
1558
|
+
This file is a local development aid, not source: `kici init` gitignores `.kici/types/`, so each team member generates their own copy with `kici types` rather than committing it. When the Platform is unreachable, `kici types` keeps any existing copy untouched, or writes an empty stub if none exists, so type checking degrades to "no known keys" instead of failing.
|
|
2563
1559
|
|
|
2564
|
-
See [CLI reference](https://docs.kici.dev/user/cli) for the `kici types` command.
|
|
1560
|
+
See [CLI reference](https://docs.kici.dev/user/cli/authoring-and-local/#kici-types) for the `kici types` command.
|
|
2565
1561
|
|
|
2566
1562
|
---
|