@kici-dev/compiler 0.1.25 → 0.1.27
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-banner.d.ts +6 -3
- package/dist/cli-banner.js +13 -5
- package/dist/cli.js +66 -43
- package/dist/commands/check-mode.d.ts +1 -1
- package/dist/commands/compile.js +3 -3
- package/dist/commands/index.d.ts +5 -1
- package/dist/commands/index.js +6 -3
- package/dist/commands/init.js +1 -1
- package/dist/commands/local-trust-root.d.ts +6 -0
- package/dist/commands/local-trust-root.js +52 -0
- package/dist/commands/local.d.ts +39 -0
- package/dist/commands/local.js +137 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +40 -0
- package/dist/commands/logout.js +7 -0
- package/dist/commands/preview.js +1 -1
- package/dist/commands/run-banner.d.ts +31 -0
- package/dist/commands/run-banner.js +25 -0
- package/dist/commands/run-routed.d.ts +53 -0
- package/dist/commands/run-routed.js +176 -0
- package/dist/commands/run.d.ts +0 -17
- package/dist/commands/run.js +7 -50
- package/dist/commands/secrets-list.d.ts +2 -2
- package/dist/commands/secrets-list.js +4 -4
- package/dist/commands/types.d.ts +3 -3
- package/dist/commands/types.js +4 -4
- package/dist/execution/executor.js +7 -7
- package/dist/generators/secrets-dts.d.ts +9 -9
- package/dist/generators/secrets-dts.js +12 -12
- package/dist/llm-context/llms-architecture.txt +2 -2
- package/dist/llm-context/llms-cli.txt +674 -340
- package/dist/llm-context/llms-features.txt +334 -336
- package/dist/llm-context/llms-full.txt +1207 -892
- package/dist/llm-context/llms-getting-started.txt +7 -15
- package/dist/llm-context/llms-patterns.txt +5 -23
- package/dist/llm-context/llms-providers.txt +10 -1
- package/dist/llm-context/llms-sdk.txt +12 -12
- package/dist/llm-context/llms.txt +5 -5
- package/dist/local-executor/secret-loader.d.ts +1 -1
- package/dist/local-plane/orchestrator-process.d.ts +65 -0
- package/dist/local-plane/orchestrator-process.js +115 -0
- package/dist/local-plane/paths.d.ts +39 -0
- package/dist/local-plane/paths.js +54 -0
- package/dist/local-plane/plane-manager.d.ts +151 -0
- package/dist/local-plane/plane-manager.js +340 -0
- package/dist/local-plane/plane-seed.d.ts +76 -0
- package/dist/local-plane/plane-seed.js +59 -0
- package/dist/local-plane/plane-trigger.d.ts +62 -0
- package/dist/local-plane/plane-trigger.js +97 -0
- package/dist/local-plane/platform-attach.d.ts +53 -0
- package/dist/local-plane/platform-attach.js +111 -0
- package/dist/local-plane/postgres.d.ts +44 -0
- package/dist/local-plane/postgres.js +165 -0
- package/dist/local-plane/resolve-plane.d.ts +37 -0
- package/dist/local-plane/resolve-plane.js +76 -0
- package/dist/local-plane/run-follow.d.ts +41 -0
- package/dist/local-plane/run-follow.js +104 -0
- package/dist/local-plane/scaler-config.d.ts +54 -0
- package/dist/local-plane/scaler-config.js +114 -0
- package/dist/local-plane/secret-seed.d.ts +59 -0
- package/dist/local-plane/secret-seed.js +112 -0
- package/dist/local-plane/source-provider.d.ts +43 -0
- package/dist/local-plane/source-provider.js +165 -0
- package/dist/local-plane/trusted-routing.d.ts +32 -0
- package/dist/local-plane/trusted-routing.js +47 -0
- package/dist/lockfile/generator.js +7 -7
- package/dist/remote/dashboard-client.d.ts +7 -7
- package/dist/remote/dashboard-client.js +7 -7
- package/dist/remote/platform-client.d.ts +1 -1
- package/dist/remote/secret-loader.d.ts +18 -0
- package/dist/{local-executor → remote}/secret-loader.js +4 -4
- package/dist/remote/secret-upload.d.ts +1 -1
- package/dist/remote/secret-upload.js +2 -2
- package/dist/remote/uploader.d.ts +1 -1
- package/dist/remote/uploader.js +2 -2
- package/dist/templates/agents-md.d.ts +1 -1
- package/dist/templates/agents-md.js +2 -2
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/job-executor.js +1 -1
- package/dist/test-runner/runs-on-display.d.ts +9 -0
- package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
- package/dist/test-runner/step-context.d.ts +1 -1
- package/dist/test-runner/step-context.js +2 -2
- package/dist/types.d.ts +6 -6
- package/package.json +7 -4
- package/sbom.spdx.json +9884 -1459
- package/dist/local-executor/dag-scheduler.js +0 -183
- package/dist/local-executor/index.js +0 -378
- package/dist/local-executor/job-runner.js +0 -411
- package/dist/local-executor/materializer.js +0 -132
- package/dist/local-executor/output-streamer.js +0 -168
- package/dist/local-executor/payload-generator.js +0 -138
- package/dist/local-executor/picker.js +0 -109
- package/dist/local-executor/to-event-payload.js +0 -21
- package/dist/local-executor/types.js +0 -2
- package/dist/local-executor/workflow-lock.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# KiCI Workflow features
|
|
2
2
|
|
|
3
|
-
This bundle covers: Workflow features: concurrency,
|
|
3
|
+
This bundle covers: Workflow features: concurrency, contexts, secrets, approvals, provenance, events.
|
|
4
4
|
|
|
5
5
|
## Account and sign-in
|
|
6
6
|
|
|
@@ -107,7 +107,7 @@ job('deploy', {
|
|
|
107
107
|
});
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
`approval: true` holds the element until **any** org member who can act on approvals signs off — anyone with the `
|
|
110
|
+
`approval: true` holds the element until **any** org member who can act on approvals signs off — anyone with the `contexts:write` or `ci_trust:write` permission. Use it when you want a manual gate without restricting who may release it.
|
|
111
111
|
|
|
112
112
|
### Approver list (AND)
|
|
113
113
|
|
|
@@ -228,7 +228,7 @@ A `when: 'drift'` gate on a step without a `check` facet, or at job/workflow sco
|
|
|
228
228
|
|
|
229
229
|
## Mandatory vs. explicit gates
|
|
230
230
|
|
|
231
|
-
`approval` is the **explicit** gate — a deliberate "pause for a human here" written by the workflow author. It composes with the **mandatory** gate an operator can attach to a protected
|
|
231
|
+
`approval` is the **explicit** gate — a deliberate "pause for a human here" written by the workflow author. It composes with the **mandatory** gate an operator can attach to a protected context via required reviewers (see [Contexts](https://docs.kici.dev/user/contexts/#required-reviewers)). When both apply to the same job, all clauses from both sources must be satisfied before the job is released. The two funnel into one held-element mechanism, so the dashboard queue and `kici approve` work the same way regardless of which source held the element.
|
|
232
232
|
|
|
233
233
|
## Approving from the CLI
|
|
234
234
|
|
|
@@ -262,12 +262,14 @@ kici run remote deploy-prod --approve-all
|
|
|
262
262
|
|
|
263
263
|
`--approve-all` is **run-scoped** — it only auto-approves holds belonging to the run this invocation dispatched; there is no fleet-wide or account-wide auto-approve. Eligibility is still enforced per hold: if you are not eligible for a gate, that gate still blocks. Each auto-approved gate prints its payload before resolving and is recorded distinctly in the audit trail (`held_run.auto_approve`).
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
`--approve-all` is honored in non-interactive runs too: with `--json` or `--quiet` (no TTY), the flag still auto-approves each eligible gate as it appears instead of hanging or printing out-of-band instructions. Hold notices are routed to stderr so `--json` stdout stays pure machine-readable output.
|
|
266
|
+
|
|
267
|
+
You can also approve from the dashboard approval queue. See [Dashboard](https://docs.kici.dev/user/dashboard/contexts-and-secrets/#approval-queue).
|
|
266
268
|
|
|
267
269
|
## See also
|
|
268
270
|
|
|
269
271
|
- [Idempotent steps](https://docs.kici.dev/user/idempotent-steps/) — the check/apply step facet that drift gates build on.
|
|
270
|
-
- [
|
|
272
|
+
- [Contexts](https://docs.kici.dev/user/contexts/) — operator-required reviewers on protected contexts.
|
|
271
273
|
- [Approval gates (operator guide)](https://docs.kici.dev/operator/approvals/) — teams, the approval queue, expiry, and self-approval.
|
|
272
274
|
- [Approval gates (architecture)](https://docs.kici.dev/architecture/approvals/) — the unified hold model and the step-level round-trip.
|
|
273
275
|
|
|
@@ -329,7 +331,7 @@ group: () => 'deploy';
|
|
|
329
331
|
group: (ctx) => `deploy-${ctx.event.targetBranch ?? 'default'}`;
|
|
330
332
|
```
|
|
331
333
|
|
|
332
|
-
The workflow-level group function is always evaluated **agent-side** at runtime -- the lock file records only that a group function exists (`hasGroup: true`), not the function itself. The agent loads the workflow source, calls the group function with `{ branch, event }`, and reports the evaluated key back to the orchestrator before step execution begins. This differs from job-level `concurrencyGroup` (see [
|
|
334
|
+
The workflow-level group function is always evaluated **agent-side** at runtime -- the lock file records only that a group function exists (`hasGroup: true`), not the function itself. The agent loads the workflow source, calls the group function with `{ branch, event }`, and reports the evaluated key back to the orchestrator before step execution begins. This differs from job-level `concurrencyGroup` (see [Contexts](https://docs.kici.dev/user/contexts/#concurrency-groups)), where the compiler performs purity analysis and can inline pure functions for orchestrator-side evaluation.
|
|
333
335
|
|
|
334
336
|
## cancelInProgress mode
|
|
335
337
|
|
|
@@ -422,7 +424,7 @@ workflow('deploy', {
|
|
|
422
424
|
jobs: [
|
|
423
425
|
job('deploy-staging', {
|
|
424
426
|
runsOn: 'linux',
|
|
425
|
-
|
|
427
|
+
context: 'staging',
|
|
426
428
|
steps: [
|
|
427
429
|
/* ... */
|
|
428
430
|
],
|
|
@@ -464,11 +466,11 @@ workflow('deploy', {
|
|
|
464
466
|
});
|
|
465
467
|
```
|
|
466
468
|
|
|
467
|
-
## Interaction with
|
|
469
|
+
## Interaction with context protection
|
|
468
470
|
|
|
469
|
-
When a workflow has both `concurrency` and `
|
|
471
|
+
When a workflow has both `concurrency` and `context` protection rules:
|
|
470
472
|
|
|
471
|
-
1.
|
|
473
|
+
1. Context protection gates (required reviewers, wait timer) apply first
|
|
472
474
|
2. Concurrency group check happens after protection gates pass
|
|
473
475
|
3. If the run is queued by concurrency, it keeps its protection approval
|
|
474
476
|
|
|
@@ -480,17 +482,15 @@ Queued runs can be cancelled before they start executing. The cancel request rem
|
|
|
480
482
|
|
|
481
483
|
## Job-level concurrency groups
|
|
482
484
|
|
|
483
|
-
In addition to workflow-level concurrency, individual jobs can define their own concurrency group via the `concurrencyGroup` property. This controls concurrent execution at the job level rather than the workflow level. See [
|
|
485
|
+
In addition to workflow-level concurrency, individual jobs can define their own concurrency group via the `concurrencyGroup` property. This controls concurrent execution at the job level rather than the workflow level. See [Contexts — concurrency groups](https://docs.kici.dev/user/contexts/#concurrency-groups) for details.
|
|
484
486
|
|
|
485
487
|
## Local execution
|
|
486
488
|
|
|
487
|
-
`kici run local` honors workflow-level `concurrency` per-machine, per-user. The `group` callback is evaluated against the simulated event identically to the remote orchestrator path; `cancelInProgress` carries the same semantics — `true` interrupts the holder via `SIGTERM` (escalating to `SIGKILL` after a grace window) and proceeds with the new run, while `false` queues the new invocation in FIFO order until the holder finishes.
|
|
489
|
+
`kici run <event> --local` honors workflow-level `concurrency` per-machine, per-user. The `group` callback is evaluated against the simulated event identically to the remote orchestrator path; `cancelInProgress` carries the same semantics — `true` interrupts the holder via `SIGTERM` (escalating to `SIGKILL` after a grace window) and proceeds with the new run, while `false` queues the new invocation in FIFO order until the holder finishes.
|
|
488
490
|
|
|
489
491
|
Coordination is local only. Running the same workflow on two different machines does not serialize across them — that requires the orchestrator. For full cross-host enforcement (queueing across agents, dashboard visibility, `max > 1`), use `kici run remote` against a deployed orchestrator.
|
|
490
492
|
|
|
491
|
-
Lock files live under `$XDG_RUNTIME_DIR/kici-local-locks/` on Linux, falling back to `os.tmpdir()/kici-local-locks-<uid>/`. A workflow whose `group` callback throws aborts the run with a clear error rather than running unprotected. See [`kici run local` — Concurrency enforcement](https://docs.kici.dev/user/cli-reference/#concurrency-enforcement) for the `KICI_LOCAL_LOCK_KILL_GRACE_MS` override and the diagnostic output emitted while contending on a busy lock.
|
|
492
|
-
|
|
493
|
-
The `kici run local --concurrency <n>` flag is a separate concept — it caps **job-level** parallelism within a single run (how many jobs from one workflow run at once), not cross-run serialization.
|
|
493
|
+
Lock files live under `$XDG_RUNTIME_DIR/kici-local-locks/` on Linux, falling back to `os.tmpdir()/kici-local-locks-<uid>/`. A workflow whose `group` callback throws aborts the run with a clear error rather than running unprotected. See [`kici run <event> --local` — Concurrency enforcement](https://docs.kici.dev/user/cli-reference/#concurrency-enforcement) for the `KICI_LOCAL_LOCK_KILL_GRACE_MS` override and the diagnostic output emitted while contending on a busy lock.
|
|
494
494
|
|
|
495
495
|
---
|
|
496
496
|
|
|
@@ -498,239 +498,19 @@ _Source: `packages/sdk/src/types.ts` (WorkflowOptions.concurrency, JobOptions.co
|
|
|
498
498
|
|
|
499
499
|
---
|
|
500
500
|
|
|
501
|
-
##
|
|
502
|
-
|
|
503
|
-
Source: https://docs.kici.dev/user/dashboard/
|
|
504
|
-
|
|
505
|
-
The KiCI dashboard is the browser interface for monitoring workflow runs, inspecting jobs and logs, and managing your organization. It signs in via OIDC and talks to KiCI over its API.
|
|
506
|
-
|
|
507
|
-
This guide is split across the following pages:
|
|
508
|
-
|
|
509
|
-
| Page | Covers |
|
|
510
|
-
| ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
511
|
-
| [Getting started](https://docs.kici.dev/user/dashboard/getting-started/) | Onboarding checklist and your organizations list |
|
|
512
|
-
| [Navigation and layout](https://docs.kici.dev/user/dashboard/navigation/) | Sidebar, mobile nav, theme, time display, shortcuts, error pages |
|
|
513
|
-
| [Runs and logs](https://docs.kici.dev/user/dashboard/runs/) | Run list, run detail, the log viewer |
|
|
514
|
-
| [Settings](https://docs.kici.dev/user/dashboard/settings/) | Members, roles, teams, keys, sources, billing, security, support access |
|
|
515
|
-
| [Workflows, diagnostics, and orchestrators](https://docs.kici.dev/user/dashboard/workflows-and-diagnostics/) | Registered workflows, infra health, per-cluster views |
|
|
516
|
-
| [Environments, secrets, and approvals](https://docs.kici.dev/user/dashboard/environments-and-secrets/) | Environments, secret scopes, approval queue |
|
|
517
|
-
| [Activity and DLQ](https://docs.kici.dev/user/dashboard/activity-and-dlq/) | Forensic activity log and dead-letter queue |
|
|
518
|
-
| [Account](https://docs.kici.dev/user/dashboard/account/) | Personal account settings |
|
|
519
|
-
|
|
520
|
-
---
|
|
521
|
-
|
|
522
|
-
## Dynamic values
|
|
523
|
-
|
|
524
|
-
Source: https://docs.kici.dev/user/dynamic-values/
|
|
525
|
-
|
|
526
|
-
Dynamic values let you compute `environment`, `env`, and `concurrencyGroup` at runtime based on the incoming event. Instead of hardcoding static strings, you pass a function that receives the normalized event envelope and returns the resolved value.
|
|
527
|
-
|
|
528
|
-
```typescript
|
|
529
|
-
job('deploy', {
|
|
530
|
-
runsOn: ['default'],
|
|
531
|
-
environment: (event) => event.targetBranch,
|
|
532
|
-
env: (event) => ({ BRANCH: event.targetBranch }),
|
|
533
|
-
concurrencyGroup: (event) => `deploy-${event.targetBranch}`,
|
|
534
|
-
steps: [
|
|
535
|
-
/* ... */
|
|
536
|
-
],
|
|
537
|
-
});
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
```typescript
|
|
541
|
-
job('deploy', {
|
|
542
|
-
runsOn: 'default',
|
|
543
|
-
// One shape everywhere: branch on the normalized event type.
|
|
544
|
-
environment: (event) => (event.type === 'pull_request' ? 'preview' : 'production'),
|
|
545
|
-
steps: [
|
|
546
|
-
/* ... */
|
|
547
|
-
],
|
|
548
|
-
});
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
## How it works
|
|
552
|
-
|
|
553
|
-
When you define a dynamic value as a function, the compiler analyzes it at compile time to determine whether it is **pure** (can be evaluated without cloning the repo or running an init job).
|
|
554
|
-
|
|
555
|
-
### Pure functions (inline evaluation)
|
|
556
|
-
|
|
557
|
-
A pure function is one that:
|
|
558
|
-
|
|
559
|
-
- Is synchronous (no `async`/`await`)
|
|
560
|
-
- Only references its parameters and local variables
|
|
561
|
-
- Does not import or require external modules
|
|
562
|
-
- Does not access globals like `process`, `fetch`, `console`, `setTimeout`, etc.
|
|
563
|
-
- Uses only safe built-in constructors: `String`, `Number`, `Boolean`, `Array`, `Object`, `JSON`, `Math`, `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `encodeURIComponent`, `decodeURIComponent`, `encodeURI`, `decodeURI`
|
|
564
|
-
- Does not use `this`, `new`, `class`, `throw`, `try`/`catch`, `delete`, `var`, `yield`, or mutation operators (`++`, `--`, `+=`, etc.)
|
|
565
|
-
|
|
566
|
-
When the compiler detects a pure function, it serializes the function source directly into the lock file as an inline expression. At dispatch time, the orchestrator evaluates the expression in a sandboxed VM context (~0ms overhead) instead of dispatching an init job.
|
|
567
|
-
|
|
568
|
-
**Examples of pure functions:**
|
|
569
|
-
|
|
570
|
-
```typescript
|
|
571
|
-
// Simple branch extraction
|
|
572
|
-
environment: (event) => event.targetBranch;
|
|
573
|
-
|
|
574
|
-
// Object literal with string operations
|
|
575
|
-
env: (event) => ({ BRANCH: event.targetBranch });
|
|
576
|
-
|
|
577
|
-
// Concatenation with event data
|
|
578
|
-
concurrencyGroup: (event) => `deploy-${event.targetBranch}`;
|
|
579
|
-
|
|
580
|
-
// Using safe globals
|
|
581
|
-
env: (event) => ({ UPPER: String(event.targetBranch).toUpperCase() });
|
|
582
|
-
|
|
583
|
-
// Local variables are fine
|
|
584
|
-
environment: (event) => {
|
|
585
|
-
const parts = event.targetBranch.split('/');
|
|
586
|
-
return parts[parts.length - 1];
|
|
587
|
-
};
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
### Impure functions (init-job evaluation)
|
|
591
|
-
|
|
592
|
-
If the compiler determines a function is impure, it emits a warning during compilation and falls back to the two-phase init model. This means:
|
|
593
|
-
|
|
594
|
-
1. The orchestrator dispatches a special `__init__` job to a builder agent
|
|
595
|
-
2. The builder agent clones the repository and evaluates the function
|
|
596
|
-
3. The resolved values are sent back to the orchestrator
|
|
597
|
-
4. The orchestrator dispatches the real execution job with the resolved values
|
|
598
|
-
|
|
599
|
-
This adds approximately 5-10 seconds of overhead for cloning and evaluation.
|
|
600
|
-
|
|
601
|
-
**Examples of impure functions (will use init job):**
|
|
602
|
-
|
|
603
|
-
```typescript
|
|
604
|
-
// Async functions cannot be inlined
|
|
605
|
-
environment: async (event) => await lookupEnv(event.targetBranch);
|
|
606
|
-
|
|
607
|
-
// External module references
|
|
608
|
-
env: (event) => {
|
|
609
|
-
const config = require('./config');
|
|
610
|
-
return config.env;
|
|
611
|
-
};
|
|
612
|
-
|
|
613
|
-
// Process/global access
|
|
614
|
-
environment: (event) => process.env.DEFAULT_ENV || 'staging';
|
|
615
|
-
|
|
616
|
-
// Dynamic imports
|
|
617
|
-
env: async (event) => {
|
|
618
|
-
const m = await import('./config.js');
|
|
619
|
-
return m.default;
|
|
620
|
-
};
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
## Performance comparison
|
|
624
|
-
|
|
625
|
-
| Evaluation path | Overhead | When used |
|
|
626
|
-
| ------------------------------------ | -------- | ------------------------------------------------------------------- |
|
|
627
|
-
| Static value (string/object literal) | ~0ms | `environment: 'staging'` |
|
|
628
|
-
| Inline expression (pure function) | ~0ms | `environment: (event) => event.targetBranch` |
|
|
629
|
-
| Init job (impure function) | ~5-10s | `environment: async (event) => await lookupEnv(event.targetBranch)` |
|
|
630
|
-
|
|
631
|
-
## Tips
|
|
632
|
-
|
|
633
|
-
- **Write pure functions whenever possible** to avoid the init-job delay. Most environment and env computations only need the event payload data.
|
|
634
|
-
- **Check compiler warnings** -- the compiler tells you when a function is classified as impure and explains why.
|
|
635
|
-
- **Runtime errors in inline expressions cause immediate job failure.** There is no fallback to the init-job path. If your pure function throws at runtime (e.g., accessing a property on `undefined`), the job fails immediately.
|
|
636
|
-
- **The event parameter is the normalized event envelope** — the same shape rules receive as `ctx.event`: `{ type, action, targetBranch, sourceBranch, changedFiles, payload, … }` (see the [event payload reference](https://docs.kici.dev/user/sdk/event-payloads/) for the complete schema). Narrow on `event.type` (`'push'`, `'pull_request'`, `'tag'`, …) to branch per trigger kind. The raw provider webhook body is nested at `event.payload` (for GitHub pushes: `payload.ref`, `payload.after`, `payload.repository`, …).
|
|
637
|
-
|
|
638
|
-
---
|
|
639
|
-
|
|
640
|
-
## Environment variables
|
|
641
|
-
|
|
642
|
-
Source: https://docs.kici.dev/user/env-vars/
|
|
643
|
-
|
|
644
|
-
The KiCI CLI reads the following environment variables to customize its behavior. OAuth login (`kici login` without `--token`) defaults `KICI_PLATFORM_URL`, `KICI_OIDC_ISSUER`, and `KICI_OIDC_CLIENT_ID` to the hosted KiCI Platform, so `kici login` works with no configuration. Set them only to target another KiCI environment (e.g. a testing instance) or a custom OIDC provider.
|
|
645
|
-
|
|
646
|
-
## Authentication
|
|
647
|
-
|
|
648
|
-
| Variable | Description | Default |
|
|
649
|
-
| --------------------- | -------------------------------------- | -------------------------------------------- |
|
|
650
|
-
| `KICI_OIDC_ISSUER` | OIDC issuer URL for authentication | `https://auth.kici.dev/realms/kici-internal` |
|
|
651
|
-
| `KICI_OIDC_CLIENT_ID` | OIDC client ID for the CLI application | `kici-cli` |
|
|
652
|
-
| `KICI_PLATFORM_URL` | Platform API base URL | `https://api.kici.dev` |
|
|
653
|
-
| `KICI_CONFIG_DIR` | Override the KiCI config directory | `~/.kici` |
|
|
654
|
-
|
|
655
|
-
## Browser behavior
|
|
656
|
-
|
|
657
|
-
| Variable | Description | Default |
|
|
658
|
-
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
659
|
-
| `KICI_BROWSER_CMD` | Custom browser command for OAuth login. Supports `{url}` placeholder. Set to `none` to suppress browser opening and print the URL to stdout instead. | Uses the system default browser via the `open` package |
|
|
660
|
-
| `KICI_CALLBACK_PORT` | Fixed port for the OAuth PKCE callback server. Useful when firewall rules require a known port. | Random available port |
|
|
661
|
-
|
|
662
|
-
## Development
|
|
663
|
-
|
|
664
|
-
| Variable | Description | Default |
|
|
665
|
-
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
|
666
|
-
| `KICI_DEV` | Enable development mode. When `true`, uses prerelease-compatible version ranges (`>=0.0.1-0`) for dev dependencies and skips npm version resolution. | unset |
|
|
667
|
-
| `KICI_DEBUG` | Enable debug logging. When `true`, prints verbose diagnostics (SDK alias resolution, step-level debug logs, stack traces on errors). Equivalent to the `--debug` CLI flag. | unset |
|
|
668
|
-
|
|
669
|
-
## Usage examples
|
|
670
|
-
|
|
671
|
-
### CI/CD environment
|
|
672
|
-
|
|
673
|
-
Authenticate with a pre-existing API key (no browser needed):
|
|
674
|
-
|
|
675
|
-
```bash
|
|
676
|
-
kici login --token <<< "$KICI_API_KEY"
|
|
677
|
-
```
|
|
678
|
-
|
|
679
|
-
### Targeting another environment or custom OIDC provider
|
|
680
|
-
|
|
681
|
-
`kici login` targets the hosted KiCI Platform by default. To point the CLI at another KiCI environment (e.g. a testing instance) or a custom OIDC provider, override the defaults:
|
|
682
|
-
|
|
683
|
-
```bash
|
|
684
|
-
export KICI_OIDC_ISSUER=https://your-idp.example.com
|
|
685
|
-
export KICI_OIDC_CLIENT_ID=your-client-id
|
|
686
|
-
export KICI_PLATFORM_URL=https://your-platform.example.com
|
|
687
|
-
kici login
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
### Headless SSH session
|
|
691
|
-
|
|
692
|
-
The CLI auto-detects headless environments and uses the device flow. To force PKCE with URL output instead:
|
|
693
|
-
|
|
694
|
-
```bash
|
|
695
|
-
export KICI_BROWSER_CMD=none
|
|
696
|
-
kici login
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
This prints the authorization URL to stdout as `KICI_AUTH_URL=<url>`. Open the URL in any browser to complete authentication.
|
|
700
|
-
|
|
701
|
-
### Fixed callback port
|
|
702
|
-
|
|
703
|
-
When behind a firewall or using port forwarding:
|
|
704
|
-
|
|
705
|
-
```bash
|
|
706
|
-
export KICI_CALLBACK_PORT=19876
|
|
707
|
-
kici login
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
### Custom config location
|
|
711
|
-
|
|
712
|
-
Store the KiCI config in a non-default location:
|
|
713
|
-
|
|
714
|
-
```bash
|
|
715
|
-
export KICI_CONFIG_DIR=/tmp/kici-test
|
|
716
|
-
kici login
|
|
717
|
-
```
|
|
718
|
-
|
|
719
|
-
---
|
|
720
|
-
|
|
721
|
-
## Environments
|
|
501
|
+
## Contexts
|
|
722
502
|
|
|
723
|
-
Source: https://docs.kici.dev/user/
|
|
503
|
+
Source: https://docs.kici.dev/user/contexts/
|
|
724
504
|
|
|
725
|
-
<!-- help:
|
|
505
|
+
<!-- help:contexts-list#overview -->
|
|
726
506
|
|
|
727
|
-
|
|
507
|
+
Contexts are named deployment targets (like staging or production) that control where your workflow jobs run. Each context can have its own variables, secrets, and protection rules to gate deployments.
|
|
728
508
|
|
|
729
|
-
<!-- /help:
|
|
509
|
+
<!-- /help:contexts-list -->
|
|
730
510
|
|
|
731
|
-
<!-- help:
|
|
511
|
+
<!-- help:contexts-protection#protection-rules -->
|
|
732
512
|
|
|
733
|
-
Protection rules control when jobs targeting
|
|
513
|
+
Protection rules control when jobs targeting a context can execute.
|
|
734
514
|
|
|
735
515
|
Available rules:
|
|
736
516
|
|
|
@@ -739,24 +519,24 @@ Available rules:
|
|
|
739
519
|
- **Wait timers** — delay execution for a fixed period.
|
|
740
520
|
- **Concurrency limits** — prevent collisions between parallel deployments.
|
|
741
521
|
|
|
742
|
-
<!-- /help:
|
|
522
|
+
<!-- /help:contexts-protection -->
|
|
743
523
|
|
|
744
|
-
|
|
524
|
+
Contexts represent deployment targets like `staging`, `production`, or `review/PR-*`. Each context can have its own variables, bound secrets, and protection rules that control when and how jobs targeting that context can execute.
|
|
745
525
|
|
|
746
526
|
## Overview
|
|
747
527
|
|
|
748
|
-
|
|
528
|
+
A context in KiCI provides:
|
|
749
529
|
|
|
750
530
|
- **Variables** -- non-secret key-value configuration (e.g., `API_URL`, `CLUSTER_NAME`)
|
|
751
|
-
- **Scoped secrets** -- encrypted values bound to the
|
|
531
|
+
- **Scoped secrets** -- encrypted values bound to the context via scope bindings
|
|
752
532
|
- **Protection rules** -- branch restrictions, required reviewers, wait timers, and concurrency limits
|
|
753
533
|
- **Per-source overrides** -- repositories can override unlocked variables for their own deployments
|
|
754
534
|
|
|
755
535
|
## SDK API
|
|
756
536
|
|
|
757
|
-
### Job-level
|
|
537
|
+
### Job-level context property
|
|
758
538
|
|
|
759
|
-
The `
|
|
539
|
+
The `context` property is set on a job, not a workflow or step:
|
|
760
540
|
|
|
761
541
|
```typescript
|
|
762
542
|
import { workflow, job, step, push } from '@kici-dev/sdk';
|
|
@@ -766,16 +546,16 @@ export default workflow('deploy', {
|
|
|
766
546
|
jobs: [
|
|
767
547
|
job('deploy-staging', {
|
|
768
548
|
runsOn: 'default',
|
|
769
|
-
|
|
549
|
+
context: 'staging',
|
|
770
550
|
steps: [
|
|
771
551
|
step('deploy', async (ctx) => {
|
|
772
|
-
// ctx.
|
|
773
|
-
console.log(`Deploying to ${ctx.
|
|
774
|
-
// ctx.secrets provides async get/expose/has methods for
|
|
552
|
+
// ctx.context is the resolved context name
|
|
553
|
+
console.log(`Deploying to ${ctx.context}`);
|
|
554
|
+
// ctx.secrets provides async get/expose/has methods for context-bound secrets
|
|
775
555
|
const dbPassword = await ctx.secrets.get('DB_PASSWORD');
|
|
776
556
|
// Environment variables are in ctx.env
|
|
777
557
|
const apiUrl = ctx.env.API_URL;
|
|
778
|
-
await ctx.$`deploy --target ${ctx.
|
|
558
|
+
await ctx.$`deploy --target ${ctx.context}`;
|
|
779
559
|
}),
|
|
780
560
|
],
|
|
781
561
|
}),
|
|
@@ -783,54 +563,54 @@ export default workflow('deploy', {
|
|
|
783
563
|
});
|
|
784
564
|
```
|
|
785
565
|
|
|
786
|
-
### Dynamic
|
|
566
|
+
### Dynamic contexts
|
|
787
567
|
|
|
788
|
-
The
|
|
568
|
+
The context name can be a string or a function (sync or async) for dynamic contexts (e.g., per-PR review contexts). The function receives the normalized event envelope, with the raw provider body nested at `event.payload`:
|
|
789
569
|
|
|
790
570
|
```typescript
|
|
791
571
|
job('deploy-review', {
|
|
792
572
|
runsOn: 'default',
|
|
793
|
-
|
|
573
|
+
context: (event) => `review/PR-${event.payload.pull_request.number}`,
|
|
794
574
|
steps: [
|
|
795
575
|
step('deploy', async (ctx) => {
|
|
796
|
-
// ctx.
|
|
797
|
-
await ctx.$`deploy-preview --env ${ctx.
|
|
576
|
+
// ctx.context is 'review/PR-123' (resolved at runtime)
|
|
577
|
+
await ctx.$`deploy-preview --env ${ctx.context}`;
|
|
798
578
|
}),
|
|
799
579
|
],
|
|
800
580
|
});
|
|
801
581
|
```
|
|
802
582
|
|
|
803
|
-
A pure function like the one above (see [Dynamic values](https://docs.kici.dev/user/dynamic-values/)) is evaluated inline at dispatch with no init-job overhead. Dynamic
|
|
583
|
+
A pure function like the one above (see [Dynamic values](https://docs.kici.dev/user/dynamic-values/)) is evaluated inline at dispatch with no init-job overhead. Dynamic contexts that match a glob pattern (e.g., `review/*`) inherit the pattern's configuration, variables, and protection rules.
|
|
804
584
|
|
|
805
|
-
### Multiple
|
|
585
|
+
### Multiple contexts per job
|
|
806
586
|
|
|
807
|
-
A job can bind more than one
|
|
587
|
+
A job can bind more than one context with `contexts`, an ordered array. This lets a single job draw secrets and variables from several contexts at once — for example a shared `staging` context plus a `my-testing` context that carries test-only variables:
|
|
808
588
|
|
|
809
589
|
```typescript
|
|
810
590
|
job('deploy', {
|
|
811
591
|
runsOn: 'default',
|
|
812
|
-
|
|
592
|
+
contexts: ['staging', 'my-testing'],
|
|
813
593
|
steps: [
|
|
814
594
|
step('deploy', async (ctx) => {
|
|
815
|
-
// ctx.secrets and ctx.env carry the merged set from both
|
|
595
|
+
// ctx.secrets and ctx.env carry the merged set from both contexts
|
|
816
596
|
const dbUrl = await ctx.secrets.get('DB_URL');
|
|
817
597
|
}),
|
|
818
598
|
],
|
|
819
599
|
});
|
|
820
600
|
```
|
|
821
601
|
|
|
822
|
-
- `
|
|
823
|
-
- Each array entry is a static name or a function of the event, resolved per element exactly like a single dynamic
|
|
602
|
+
- `context` (singular) and `contexts` (array) are mutually exclusive — setting both is a compile error. `context: 'staging'` is exactly equivalent to `contexts: ['staging']`.
|
|
603
|
+
- Each array entry is a static name or a function of the event, resolved per element exactly like a single dynamic context.
|
|
824
604
|
|
|
825
|
-
**Merge order — last wins.** All bound
|
|
605
|
+
**Merge order — last wins.** All bound contexts are resolved on every dispatch (webhook, scheduled, and test runs alike) and merged in array order. When the same secret or variable key is defined in more than one context, the later entry in the array wins. With `contexts: ['staging', 'my-testing']`, a key defined in both resolves to `my-testing`'s value; keys defined in only one are preserved. The longest-scope-path-wins rule still applies _within_ each context.
|
|
826
606
|
|
|
827
|
-
**Protection rules combine all-must-pass.** A job must satisfy **every** bound
|
|
607
|
+
**Protection rules combine all-must-pass.** A job must satisfy **every** bound context's gates — adding a context can never loosen access. Branch restrictions, trigger-type filters, and repo patterns must pass for all contexts; the minimum trust tier is the most restrictive across them; required reviewers are the union of all contexts' reviewers; the wait timer is the longest; and the hold expiry is the shortest. If a run is gated out, the rejection names which context and which rule rejected it (visible via `kici runs show <run-id>` and the run's rejection reason), so a mutually-exclusive set of rules surfaces as a clear failure rather than a silent perpetual rejection.
|
|
828
608
|
|
|
829
|
-
**Skip-on-test (allow-and-warn).** On a test or local run (`kici run remote`, `kici run local`), a bound
|
|
609
|
+
**Skip-on-test (allow-and-warn).** On a test or local run (`kici run remote`, `kici run <event> --local`), a bound context never rejects the run. Any bound context that disallows local execution (`allowLocalExecution: false`) — or that is not configured — is **skipped**: its variables and secrets are omitted from the merge and its gates are not evaluated. The run proceeds, and a user-visible warning naming the skipped context(s) is shown both on the `kici run remote` CLI output and on the dashboard run view. This makes the test-only-variables pattern work: with `contexts: ['staging', 'my-testing']` where only `my-testing` allows local execution, a test run resolves just `my-testing`'s variables and warns that `staging` was skipped. If every bound context is skipped, the job runs with no environment variables. This is intentionally different from a fixture `secrets:` mapping, which is fail-closed — see the [testing guide](https://docs.kici.dev/user/testing-guide/).
|
|
830
610
|
|
|
831
|
-
**Unconfigured
|
|
611
|
+
**Unconfigured contexts contribute nothing at dispatch.** At dispatch time a bound context name with no matching configured context (and no matching glob context) simply adds no variables, secrets, or protection rules — the job still runs, exactly as a single dynamic context resolving to an as-yet-unconfigured name does today.
|
|
832
612
|
|
|
833
|
-
**Registration rejects a provably-unsatisfiable binding.** When a workflow is registered, KiCI statically checks every multi-
|
|
613
|
+
**Registration rejects a provably-unsatisfiable binding.** When a workflow is registered, KiCI statically checks every multi-context binding: a bound context that does not exist, a disabled one, or two contexts with mutually-exclusive fixed branch / trigger-type / repository restrictions (no value can satisfy both) makes the binding provably unsatisfiable, and the registration is rejected with a precise message naming the job, the contexts, and the rule — for example `unsatisfiable context binding: job 'deploy' binds contexts [staging, my-testing] with mutually exclusive branch restrictions (no value satisfies all bound contexts)`. Bindings whose restrictions use globs are undecidable at registration and fall through to the dispatch-time gate check instead.
|
|
834
614
|
|
|
835
615
|
### Job-level environment variables
|
|
836
616
|
|
|
@@ -839,7 +619,7 @@ The `env` property on a job provides static or dynamic environment variables:
|
|
|
839
619
|
```typescript
|
|
840
620
|
job('deploy', {
|
|
841
621
|
runsOn: 'default',
|
|
842
|
-
|
|
622
|
+
context: 'production',
|
|
843
623
|
env: { DEPLOY_TARGET: 'us-east-1' },
|
|
844
624
|
// Or dynamic:
|
|
845
625
|
// env: (event) => ({ DEPLOY_SHA: event.payload.after?.slice(0, 7) }),
|
|
@@ -854,14 +634,14 @@ job('deploy', {
|
|
|
854
634
|
|
|
855
635
|
### Concurrency groups
|
|
856
636
|
|
|
857
|
-
Jobs can define their own concurrency groups to control concurrent execution within
|
|
637
|
+
Jobs can define their own concurrency groups to control concurrent execution within a context. For workflow-level concurrency (which applies to all jobs in a workflow), see [Concurrency groups](https://docs.kici.dev/user/concurrency/).
|
|
858
638
|
|
|
859
|
-
Control concurrent deployments to the same
|
|
639
|
+
Control concurrent deployments to the same context:
|
|
860
640
|
|
|
861
641
|
```typescript
|
|
862
642
|
job('deploy', {
|
|
863
643
|
runsOn: 'default',
|
|
864
|
-
|
|
644
|
+
context: 'production',
|
|
865
645
|
concurrencyGroup: 'production-api',
|
|
866
646
|
// Or dynamic:
|
|
867
647
|
// concurrencyGroup: (event) => `review-${event.payload.pull_request.number}`,
|
|
@@ -871,17 +651,17 @@ job('deploy', {
|
|
|
871
651
|
});
|
|
872
652
|
```
|
|
873
653
|
|
|
874
|
-
If no `concurrencyGroup` is specified, the
|
|
654
|
+
If no `concurrencyGroup` is specified, the context name is used as the default concurrency group. For a job bound to multiple contexts, the default is the **first** bound context's name.
|
|
875
655
|
|
|
876
656
|
### Step context
|
|
877
657
|
|
|
878
658
|
Inside a step, the `ctx` object provides:
|
|
879
659
|
|
|
880
|
-
| Property
|
|
881
|
-
|
|
|
882
|
-
| `ctx.
|
|
883
|
-
| `ctx.env`
|
|
884
|
-
| `ctx.secrets`
|
|
660
|
+
| Property | Type | Description |
|
|
661
|
+
| ------------- | ------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
662
|
+
| `ctx.context` | `string \| undefined` | Resolved context name (undefined for jobs without context) |
|
|
663
|
+
| `ctx.env` | `Record<string, string \| undefined>` | Environment variables (merged from system, org, source, and job-level `env`) |
|
|
664
|
+
| `ctx.secrets` | `StepSecretsTyped` | Async accessor for bound secrets (get, expose, has, getMeta, list, mountFile, exposeFile) |
|
|
885
665
|
|
|
886
666
|
| Method | Returns | Description |
|
|
887
667
|
| -------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -898,25 +678,25 @@ The full secrets API — including `SecretFileOptions`, log masking, and the can
|
|
|
898
678
|
|
|
899
679
|
## Environment variable merge precedence
|
|
900
680
|
|
|
901
|
-
When a job targets
|
|
681
|
+
When a job targets a context, variables are merged in this order (last wins):
|
|
902
682
|
|
|
903
683
|
1. **Allowed system vars** -- `PATH`, `HOME`, etc. from the agent process
|
|
904
684
|
2. **Sandbox defaults** -- `FORCE_COLOR=1`
|
|
905
685
|
3. **KICI\_\* system vars** -- orchestrator-generated metadata
|
|
906
|
-
4. **Org-level
|
|
686
|
+
4. **Org-level context vars** -- from the dashboard, managed per-context
|
|
907
687
|
5. **Source-level overrides** -- per-repository overrides (skips locked vars)
|
|
908
688
|
6. **Job env** -- from the `env` property in the SDK
|
|
909
689
|
7. **`setEnv()` calls** -- runtime modifications within steps
|
|
910
690
|
|
|
911
|
-
> **Note:** Secrets are NOT part of the environment variable merge. They are delivered to the step context via IPC and accessed through `ctx.secrets`, not through `process.env`. See the [step context](https://docs.kici.dev/user/
|
|
691
|
+
> **Note:** Secrets are NOT part of the environment variable merge. They are delivered to the step context via IPC and accessed through `ctx.secrets`, not through `process.env`. See the [step context](https://docs.kici.dev/user/contexts/#step-context) section above.
|
|
912
692
|
|
|
913
693
|
## Protection rules
|
|
914
694
|
|
|
915
|
-
|
|
695
|
+
Contexts can have protection rules that gate job execution:
|
|
916
696
|
|
|
917
697
|
### Branch restrictions
|
|
918
698
|
|
|
919
|
-
Limit which branches can deploy to
|
|
699
|
+
Limit which branches can deploy to a context:
|
|
920
700
|
|
|
921
701
|
```
|
|
922
702
|
Allowed branches: main, release/*
|
|
@@ -973,7 +753,7 @@ See the [CI security architecture docs](https://docs.kici.dev/architecture/secur
|
|
|
973
753
|
|
|
974
754
|
### Security approval queue
|
|
975
755
|
|
|
976
|
-
When a PR is held for security review (unknown contributor, workflow modification, or trust policy violation), it enters the security approval queue. This is separate from
|
|
756
|
+
When a PR is held for security review (unknown contributor, workflow modification, or trust policy violation), it enters the security approval queue. This is separate from context-level approval queues.
|
|
977
757
|
|
|
978
758
|
Held runs can be approved:
|
|
979
759
|
|
|
@@ -984,7 +764,7 @@ Security holds expire after a configurable timeout (default 1 hour).
|
|
|
984
764
|
|
|
985
765
|
### Concurrency limits
|
|
986
766
|
|
|
987
|
-
Control how many jobs can run simultaneously in
|
|
767
|
+
Control how many jobs can run simultaneously in a context:
|
|
988
768
|
|
|
989
769
|
```
|
|
990
770
|
Concurrency limit: 1
|
|
@@ -996,18 +776,18 @@ Strategy: queue (or cancel-pending)
|
|
|
996
776
|
|
|
997
777
|
## Dashboard management
|
|
998
778
|
|
|
999
|
-
### Creating
|
|
779
|
+
### Creating contexts
|
|
1000
780
|
|
|
1001
|
-
Navigate to **Settings >
|
|
781
|
+
Navigate to **Settings > Contexts** in the dashboard. Click **New context** to choose the context name and type (Fixed or Glob).
|
|
1002
782
|
|
|
1003
|
-
- **Fixed** -- applies to jobs that declare exactly this
|
|
1004
|
-
- **Glob** -- applies to any
|
|
783
|
+
- **Fixed** -- applies to jobs that declare exactly this context name, like `staging` or `production`
|
|
784
|
+
- **Glob** -- applies to any context name a job declares that matches the pattern, e.g. `review/*` matches a job with `context: 'review/PR-123'`
|
|
1005
785
|
|
|
1006
|
-
The
|
|
786
|
+
The contexts list shows each context's type, whether test runs may use it (the `allowLocalExecution` flag -- see the [testing guide](https://docs.kici.dev/user/testing-guide/)), and whether it is enabled.
|
|
1007
787
|
|
|
1008
|
-
###
|
|
788
|
+
### Context detail page
|
|
1009
789
|
|
|
1010
|
-
Each
|
|
790
|
+
Each context has four tabs:
|
|
1011
791
|
|
|
1012
792
|
1. **Variables** -- manage key-value pairs with lock toggles. Locked variables cannot be overridden by source-level overrides. Source overrides are managed in a sub-tab.
|
|
1013
793
|
|
|
@@ -1015,26 +795,26 @@ Each environment has four tabs:
|
|
|
1015
795
|
|
|
1016
796
|
3. **Protection** -- configure branch restrictions, required reviewers, wait timers, and concurrency limits with enable toggles for each section.
|
|
1017
797
|
|
|
1018
|
-
4. **History** -- view filtered runs targeting this
|
|
798
|
+
4. **History** -- view filtered runs targeting this context.
|
|
1019
799
|
|
|
1020
|
-
### Bound
|
|
800
|
+
### Bound contexts on runs
|
|
1021
801
|
|
|
1022
|
-
A job's bound deployment
|
|
802
|
+
A job's bound deployment contexts are shown as chips on the run detail page (in the job metadata panel) in the order the job declared them, and the distinct set across a run's jobs appears as compact chips on the run list. For a multi-context job the chips read left-to-right in merge order — later contexts override earlier ones on key collisions. A `(dynamic)` chip marks a context whose name is computed at runtime; it resolves to the real name once the run starts. A job that binds a single context shows one chip; a job that binds none shows no chip.
|
|
1023
803
|
|
|
1024
|
-
If a multi-
|
|
804
|
+
If a multi-context binding is gated out, the run's failure banner names which context and which rule rejected it (the same all-must-pass detail surfaced by `kici runs show <run-id>`).
|
|
1025
805
|
|
|
1026
806
|
### Secrets management
|
|
1027
807
|
|
|
1028
|
-
Secrets are individual encrypted values organized by scope paths (e.g., `aws/prod`, `databases/postgres`). Scopes are bound to
|
|
808
|
+
Secrets are individual encrypted values organized by scope paths (e.g., `aws/prod`, `databases/postgres`). Scopes are bound to contexts via bindings:
|
|
1029
809
|
|
|
1030
|
-
- **Scope-centric view** (Secrets page): tree view of scopes with per-scope
|
|
1031
|
-
- **
|
|
810
|
+
- **Scope-centric view** (Secrets page): tree view of scopes with per-scope context binding checkboxes
|
|
811
|
+
- **Context-centric view** (inside context detail): bound scopes, resolved secrets, add binding
|
|
1032
812
|
|
|
1033
813
|
When scope paths collide on the same key name, the longer (more specific) path wins.
|
|
1034
814
|
|
|
1035
815
|
## Type generation
|
|
1036
816
|
|
|
1037
|
-
Running `kici types` generates two augmented interfaces: `KnownSecretKeys` (union of all secret keys across all
|
|
817
|
+
Running `kici types` generates two augmented interfaces: `KnownSecretKeys` (union of all secret keys across all contexts) and `ContextSecrets` (per-context key unions):
|
|
1038
818
|
|
|
1039
819
|
```typescript
|
|
1040
820
|
interface KnownSecretKeys {
|
|
@@ -1042,13 +822,233 @@ interface KnownSecretKeys {
|
|
|
1042
822
|
API_KEY: string;
|
|
1043
823
|
}
|
|
1044
824
|
|
|
1045
|
-
interface
|
|
825
|
+
interface ContextSecrets {
|
|
1046
826
|
production: 'DB_PASSWORD' | 'API_KEY';
|
|
1047
827
|
staging: 'DB_PASSWORD';
|
|
1048
828
|
}
|
|
1049
829
|
```
|
|
1050
830
|
|
|
1051
|
-
`KnownSecretKeys` narrows `ctx.secrets.get()` and `ctx.secrets.expose()` key parameters to valid key names. `
|
|
831
|
+
`KnownSecretKeys` narrows `ctx.secrets.get()` and `ctx.secrets.expose()` key parameters to valid key names. `ContextSecrets` maps each context to its available secret key names as a string union. Dynamic contexts fall back to the full `KnownSecretKeys` union.
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## Dashboard
|
|
836
|
+
|
|
837
|
+
Source: https://docs.kici.dev/user/dashboard/
|
|
838
|
+
|
|
839
|
+
The KiCI dashboard is the browser interface for monitoring workflow runs, inspecting jobs and logs, and managing your organization. It signs in via OIDC and talks to KiCI over its API.
|
|
840
|
+
|
|
841
|
+
This guide is split across the following pages:
|
|
842
|
+
|
|
843
|
+
| Page | Covers |
|
|
844
|
+
| ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
845
|
+
| [Getting started](https://docs.kici.dev/user/dashboard/getting-started/) | Onboarding checklist and your organizations list |
|
|
846
|
+
| [Navigation and layout](https://docs.kici.dev/user/dashboard/navigation/) | Sidebar, mobile nav, theme, time display, shortcuts, error pages |
|
|
847
|
+
| [Runs and logs](https://docs.kici.dev/user/dashboard/runs/) | Run list, run detail, the log viewer |
|
|
848
|
+
| [Settings](https://docs.kici.dev/user/dashboard/settings/) | Members, roles, teams, keys, sources, billing, security, support access |
|
|
849
|
+
| [Workflows, diagnostics, and orchestrators](https://docs.kici.dev/user/dashboard/workflows-and-diagnostics/) | Registered workflows, infra health, per-cluster views |
|
|
850
|
+
| [Contexts, secrets, and approvals](https://docs.kici.dev/user/dashboard/contexts-and-secrets/) | Contexts, secret scopes, approval queue |
|
|
851
|
+
| [Activity and DLQ](https://docs.kici.dev/user/dashboard/activity-and-dlq/) | Forensic activity log and dead-letter queue |
|
|
852
|
+
| [Account](https://docs.kici.dev/user/dashboard/account/) | Personal account settings |
|
|
853
|
+
|
|
854
|
+
---
|
|
855
|
+
|
|
856
|
+
## Dynamic values
|
|
857
|
+
|
|
858
|
+
Source: https://docs.kici.dev/user/dynamic-values/
|
|
859
|
+
|
|
860
|
+
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.
|
|
861
|
+
|
|
862
|
+
```typescript
|
|
863
|
+
job('deploy', {
|
|
864
|
+
runsOn: ['default'],
|
|
865
|
+
context: (event) => event.targetBranch,
|
|
866
|
+
env: (event) => ({ BRANCH: event.targetBranch }),
|
|
867
|
+
concurrencyGroup: (event) => `deploy-${event.targetBranch}`,
|
|
868
|
+
steps: [
|
|
869
|
+
/* ... */
|
|
870
|
+
],
|
|
871
|
+
});
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
```typescript
|
|
875
|
+
job('deploy', {
|
|
876
|
+
runsOn: 'default',
|
|
877
|
+
// One shape everywhere: branch on the normalized event type.
|
|
878
|
+
context: (event) => (event.type === 'pull_request' ? 'preview' : 'production'),
|
|
879
|
+
steps: [
|
|
880
|
+
/* ... */
|
|
881
|
+
],
|
|
882
|
+
});
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
## How it works
|
|
886
|
+
|
|
887
|
+
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).
|
|
888
|
+
|
|
889
|
+
### Pure functions (inline evaluation)
|
|
890
|
+
|
|
891
|
+
A pure function is one that:
|
|
892
|
+
|
|
893
|
+
- Is synchronous (no `async`/`await`)
|
|
894
|
+
- Only references its parameters and local variables
|
|
895
|
+
- Does not import or require external modules
|
|
896
|
+
- Does not access globals like `process`, `fetch`, `console`, `setTimeout`, etc.
|
|
897
|
+
- Uses only safe built-in constructors: `String`, `Number`, `Boolean`, `Array`, `Object`, `JSON`, `Math`, `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `encodeURIComponent`, `decodeURIComponent`, `encodeURI`, `decodeURI`
|
|
898
|
+
- Does not use `this`, `new`, `class`, `throw`, `try`/`catch`, `delete`, `var`, `yield`, or mutation operators (`++`, `--`, `+=`, etc.)
|
|
899
|
+
|
|
900
|
+
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.
|
|
901
|
+
|
|
902
|
+
**Examples of pure functions:**
|
|
903
|
+
|
|
904
|
+
```typescript
|
|
905
|
+
// Simple branch extraction
|
|
906
|
+
context: (event) => event.targetBranch;
|
|
907
|
+
|
|
908
|
+
// Object literal with string operations
|
|
909
|
+
env: (event) => ({ BRANCH: event.targetBranch });
|
|
910
|
+
|
|
911
|
+
// Concatenation with event data
|
|
912
|
+
concurrencyGroup: (event) => `deploy-${event.targetBranch}`;
|
|
913
|
+
|
|
914
|
+
// Using safe globals
|
|
915
|
+
env: (event) => ({ UPPER: String(event.targetBranch).toUpperCase() });
|
|
916
|
+
|
|
917
|
+
// Local variables are fine
|
|
918
|
+
context: (event) => {
|
|
919
|
+
const parts = event.targetBranch.split('/');
|
|
920
|
+
return parts[parts.length - 1];
|
|
921
|
+
};
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
### Impure functions (init-job evaluation)
|
|
925
|
+
|
|
926
|
+
If the compiler determines a function is impure, it emits a warning during compilation and falls back to the two-phase init model. This means:
|
|
927
|
+
|
|
928
|
+
1. The orchestrator dispatches a special `__init__` job to a builder agent
|
|
929
|
+
2. The builder agent clones the repository and evaluates the function
|
|
930
|
+
3. The resolved values are sent back to the orchestrator
|
|
931
|
+
4. The orchestrator dispatches the real execution job with the resolved values
|
|
932
|
+
|
|
933
|
+
This adds approximately 5-10 seconds of overhead for cloning and evaluation.
|
|
934
|
+
|
|
935
|
+
**Examples of impure functions (will use init job):**
|
|
936
|
+
|
|
937
|
+
```typescript
|
|
938
|
+
// Async functions cannot be inlined
|
|
939
|
+
context: async (event) => await lookupEnv(event.targetBranch);
|
|
940
|
+
|
|
941
|
+
// External module references
|
|
942
|
+
env: (event) => {
|
|
943
|
+
const config = require('./config');
|
|
944
|
+
return config.env;
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
// Process/global access
|
|
948
|
+
context: (event) => process.env.DEFAULT_ENV || 'staging';
|
|
949
|
+
|
|
950
|
+
// Dynamic imports
|
|
951
|
+
env: async (event) => {
|
|
952
|
+
const m = await import('./config.js');
|
|
953
|
+
return m.default;
|
|
954
|
+
};
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
## Performance comparison
|
|
958
|
+
|
|
959
|
+
| Evaluation path | Overhead | When used |
|
|
960
|
+
| ------------------------------------ | -------- | --------------------------------------------------------------- |
|
|
961
|
+
| Static value (string/object literal) | ~0ms | `context: 'staging'` |
|
|
962
|
+
| Inline expression (pure function) | ~0ms | `context: (event) => event.targetBranch` |
|
|
963
|
+
| Init job (impure function) | ~5-10s | `context: async (event) => await lookupEnv(event.targetBranch)` |
|
|
964
|
+
|
|
965
|
+
## Tips
|
|
966
|
+
|
|
967
|
+
- **Write pure functions whenever possible** to avoid the init-job delay. Most context and env computations only need the event payload data.
|
|
968
|
+
- **Check compiler warnings** -- the compiler tells you when a function is classified as impure and explains why.
|
|
969
|
+
- **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.
|
|
970
|
+
- **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`, …).
|
|
971
|
+
|
|
972
|
+
---
|
|
973
|
+
|
|
974
|
+
## Environment variables
|
|
975
|
+
|
|
976
|
+
Source: https://docs.kici.dev/user/env-vars/
|
|
977
|
+
|
|
978
|
+
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.
|
|
979
|
+
|
|
980
|
+
## Authentication
|
|
981
|
+
|
|
982
|
+
| Variable | Description | Default |
|
|
983
|
+
| --------------------- | -------------------------------------- | -------------------------------------------- |
|
|
984
|
+
| `KICI_OIDC_ISSUER` | OIDC issuer URL for authentication | `https://auth.kici.dev/realms/kici-internal` |
|
|
985
|
+
| `KICI_OIDC_CLIENT_ID` | OIDC client ID for the CLI application | `kici-cli` |
|
|
986
|
+
| `KICI_PLATFORM_URL` | Platform API base URL | `https://api.kici.dev` |
|
|
987
|
+
| `KICI_CONFIG_DIR` | Override the KiCI config directory | `~/.kici` |
|
|
988
|
+
|
|
989
|
+
## Browser behavior
|
|
990
|
+
|
|
991
|
+
| Variable | Description | Default |
|
|
992
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
993
|
+
| `KICI_BROWSER_CMD` | Custom browser command for OAuth login. Supports `{url}` placeholder. Set to `none` to suppress browser opening and print the URL to stdout instead. | Uses the system default browser via the `open` package |
|
|
994
|
+
| `KICI_CALLBACK_PORT` | Fixed port for the OAuth PKCE callback server. Useful when firewall rules require a known port. | Random available port |
|
|
995
|
+
|
|
996
|
+
## Development
|
|
997
|
+
|
|
998
|
+
| Variable | Description | Default |
|
|
999
|
+
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
|
1000
|
+
| `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 |
|
|
1001
|
+
| `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 |
|
|
1002
|
+
|
|
1003
|
+
## Usage examples
|
|
1004
|
+
|
|
1005
|
+
### CI/CD environment
|
|
1006
|
+
|
|
1007
|
+
Authenticate with a pre-existing API key (no browser needed):
|
|
1008
|
+
|
|
1009
|
+
```bash
|
|
1010
|
+
kici login --token <<< "$KICI_API_KEY"
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
### Targeting another environment or custom OIDC provider
|
|
1014
|
+
|
|
1015
|
+
`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:
|
|
1016
|
+
|
|
1017
|
+
```bash
|
|
1018
|
+
export KICI_OIDC_ISSUER=https://your-idp.example.com
|
|
1019
|
+
export KICI_OIDC_CLIENT_ID=your-client-id
|
|
1020
|
+
export KICI_PLATFORM_URL=https://your-platform.example.com
|
|
1021
|
+
kici login
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
### Headless SSH session
|
|
1025
|
+
|
|
1026
|
+
The CLI auto-detects headless environments and uses the device flow. To force PKCE with URL output instead:
|
|
1027
|
+
|
|
1028
|
+
```bash
|
|
1029
|
+
export KICI_BROWSER_CMD=none
|
|
1030
|
+
kici login
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
This prints the authorization URL to stdout as `KICI_AUTH_URL=<url>`. Open the URL in any browser to complete authentication.
|
|
1034
|
+
|
|
1035
|
+
### Fixed callback port
|
|
1036
|
+
|
|
1037
|
+
When behind a firewall or using port forwarding:
|
|
1038
|
+
|
|
1039
|
+
```bash
|
|
1040
|
+
export KICI_CALLBACK_PORT=19876
|
|
1041
|
+
kici login
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
### Custom config location
|
|
1045
|
+
|
|
1046
|
+
Store the KiCI config in a non-default location:
|
|
1047
|
+
|
|
1048
|
+
```bash
|
|
1049
|
+
export KICI_CONFIG_DIR=/tmp/kici-test
|
|
1050
|
+
kici login
|
|
1051
|
+
```
|
|
1052
1052
|
|
|
1053
1053
|
---
|
|
1054
1054
|
|
|
@@ -1643,21 +1643,19 @@ guaranteed side-effect-free.
|
|
|
1643
1643
|
|
|
1644
1644
|
## Running in check mode
|
|
1645
1645
|
|
|
1646
|
-
`--check` and `--fail-on-drift`
|
|
1646
|
+
`--check` and `--fail-on-drift` control drift reporting on `kici run remote`:
|
|
1647
1647
|
|
|
1648
1648
|
```bash
|
|
1649
1649
|
# Apply (default): converge the workflow.
|
|
1650
|
-
kici run local
|
|
1650
|
+
kici run push --local
|
|
1651
1651
|
kici run remote my-fixture
|
|
1652
1652
|
|
|
1653
1653
|
# Check: report drift, change nothing. Always exits 0.
|
|
1654
|
-
kici run local push --check
|
|
1655
1654
|
kici run remote my-fixture --check
|
|
1656
1655
|
|
|
1657
|
-
# Check + fail on drift:
|
|
1658
|
-
#
|
|
1659
|
-
|
|
1660
|
-
kici run local push --check --fail-on-drift
|
|
1656
|
+
# Check + fail on drift: fail the run when any step reports drift. Use this as a
|
|
1657
|
+
# CI gate ("fail the build if prod has drifted").
|
|
1658
|
+
kici run remote my-fixture --check --fail-on-drift
|
|
1661
1659
|
```
|
|
1662
1660
|
|
|
1663
1661
|
`--fail-on-drift` only modifies check mode — passing it without `--check` is an
|
|
@@ -1696,7 +1694,7 @@ The two channels (Option A and Option C) compose. If you declare both, the agent
|
|
|
1696
1694
|
|
|
1697
1695
|
## Option A — `registries:` block
|
|
1698
1696
|
|
|
1699
|
-
Declare the registry in your workflow file and point its `tokenSecret` at a scoped secret using the qualified `<
|
|
1697
|
+
Declare the registry in your workflow file and point its `tokenSecret` at a scoped secret using the qualified `<context>:<secret-name>` syntax. The orchestrator resolves the token at dispatch time and the agent applies it for one `npm install` only.
|
|
1700
1698
|
|
|
1701
1699
|
```typescript
|
|
1702
1700
|
import { workflow, job, step, push } from '@kici-dev/sdk';
|
|
@@ -1713,7 +1711,7 @@ export default workflow('build', {
|
|
|
1713
1711
|
jobs: [
|
|
1714
1712
|
job('build', {
|
|
1715
1713
|
runsOn: 'default',
|
|
1716
|
-
|
|
1714
|
+
context: 'production',
|
|
1717
1715
|
steps: [
|
|
1718
1716
|
step('install-and-build', async (ctx) => {
|
|
1719
1717
|
// .kici/package.json can now reference @my-org/* packages
|
|
@@ -1729,7 +1727,7 @@ Per-field rules:
|
|
|
1729
1727
|
|
|
1730
1728
|
- **`url`** — Must be HTTPS. HTTP is permitted only for `localhost` / `127.0.0.0/8` / `::1` / `*.local` hosts, or when an operator has flipped the org-level `allow_http_npm_registries` toggle (see [`kici-admin org-settings allow-http-npm`](https://docs.kici.dev/operator/kici-admin-cli#allow-http-npm--permit-non-https-private-npm-registries)).
|
|
1731
1729
|
- **`scope`** — Optional. When present, the registry serves only that scope (`@my-org`). When absent, this entry becomes the **default** registry — at most one entry may omit `scope`.
|
|
1732
|
-
- **`tokenSecret`** — Mandatory `<
|
|
1730
|
+
- **`tokenSecret`** — Mandatory `<context>:<secret-name>`. The orchestrator looks up the secret in the named context via the per-context secret resolver. The bare name **must not** contain a colon.
|
|
1733
1731
|
- **`alwaysAuth`** — Defaults to `true`. Forces npm to send the token on every request (even GETs), which is what most managed-registry providers require.
|
|
1734
1732
|
|
|
1735
1733
|
### How tokens reach `npm install`
|
|
@@ -1759,14 +1757,14 @@ export default workflow('build', {
|
|
|
1759
1757
|
jobs: [
|
|
1760
1758
|
job('build', {
|
|
1761
1759
|
runsOn: 'default',
|
|
1762
|
-
|
|
1760
|
+
context: 'production',
|
|
1763
1761
|
steps: [step('build', async (ctx) => ctx.$`npm run build`)],
|
|
1764
1762
|
}),
|
|
1765
1763
|
],
|
|
1766
1764
|
});
|
|
1767
1765
|
```
|
|
1768
1766
|
|
|
1769
|
-
The orchestrator resolves `MY_NPM_TOKEN` from the `production`
|
|
1767
|
+
The orchestrator resolves `MY_NPM_TOKEN` from the `production` context's secret store and seeds it as `MY_NPM_TOKEN` (bare name) in the install subprocess. Your committed `.npmrc` reads it through `${MY_NPM_TOKEN}`.
|
|
1770
1768
|
|
|
1771
1769
|
This path is the right answer when:
|
|
1772
1770
|
|
|
@@ -1788,7 +1786,7 @@ export default workflow('build', {
|
|
|
1788
1786
|
jobs: [
|
|
1789
1787
|
job('mint-codeartifact-token', {
|
|
1790
1788
|
runsOn: 'default',
|
|
1791
|
-
|
|
1789
|
+
context: 'production',
|
|
1792
1790
|
steps: [
|
|
1793
1791
|
step('mint', async (ctx) => {
|
|
1794
1792
|
const awsKey = await ctx.secrets.get('AWS_ACCESS_KEY_ID');
|
|
@@ -1813,7 +1811,7 @@ export default workflow('build', {
|
|
|
1813
1811
|
}),
|
|
1814
1812
|
job('build', {
|
|
1815
1813
|
runsOn: 'default',
|
|
1816
|
-
|
|
1814
|
+
context: 'production',
|
|
1817
1815
|
needs: ['mint-codeartifact-token'],
|
|
1818
1816
|
steps: [step('build', async (ctx) => ctx.$`npm run build`)],
|
|
1819
1817
|
}),
|
|
@@ -1837,7 +1835,7 @@ registries: [
|
|
|
1837
1835
|
],
|
|
1838
1836
|
```
|
|
1839
1837
|
|
|
1840
|
-
Mint the token from a fine-grained PAT with `read:packages` scope, store it as a scoped secret in the `production`
|
|
1838
|
+
Mint the token from a fine-grained PAT with `read:packages` scope, store it as a scoped secret in the `production` context.
|
|
1841
1839
|
|
|
1842
1840
|
### GitLab Packages
|
|
1843
1841
|
|
|
@@ -1892,8 +1890,8 @@ registries: [
|
|
|
1892
1890
|
|
|
1893
1891
|
## Security model
|
|
1894
1892
|
|
|
1895
|
-
- **Per-
|
|
1896
|
-
- **Untrusted contributors get no tokens.** When a fork PR is dispatched and the contributor-trust resolution returns anything other than `trusted`, the orchestrator strips both `npmRegistries` and `installEnvSecrets` out of the dispatch. The install runs without auth and fails naturally on the first private dep — fork PRs cannot ever observe a registry token, even if a misconfigured
|
|
1893
|
+
- **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.
|
|
1894
|
+
- **Untrusted contributors get no tokens.** When a fork PR is dispatched and the contributor-trust resolution returns anything other than `trusted`, the orchestrator strips both `npmRegistries` and `installEnvSecrets` out of the dispatch. The install runs without auth and fails naturally on the first private dep — fork PRs cannot ever observe a registry token, even if a misconfigured context lacks an explicit `requiredTrustTier`.
|
|
1897
1895
|
- **Lifecycle scripts disabled.** Whenever a private registry is in scope, the agent runs the install with `--ignore-scripts` (npm or pnpm alike). A malicious `preinstall` / `postinstall` hook in committed `package.json` cannot read the synthesized token env vars, even though they exist in the install subprocess. For a pnpm workspace, the agent builds your in-repo dependency closure as a separate step **after** the install's auth is torn down, so build scripts never see the tokens either.
|
|
1898
1896
|
- **Stderr is redacted.** If the install fails, the agent masks every token literal out of the surfaced stderr / stdout chunks before logging.
|
|
1899
1897
|
- **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.
|
|
@@ -1901,12 +1899,12 @@ registries: [
|
|
|
1901
1899
|
|
|
1902
1900
|
## Reviewer-gated installs
|
|
1903
1901
|
|
|
1904
|
-
When the named install
|
|
1902
|
+
When the named install context carries a protection rule that holds — a required reviewer (`hold`) or a wait timer (`wait`) — the install gate **pauses the whole workflow dispatch** instead of rejecting it. The run is created in the `held` state, no jobs are queued, and a workflow-scoped row appears on the held-runs page with a `Workflow` scope badge.
|
|
1905
1903
|
|
|
1906
1904
|
- **Reviewer hold:** the run waits for an approver. On approval the dispatch resumes from the install gate, resolves the token, and dispatches its jobs as a normal run. On rejection the run transitions to `cancelled` — no jobs ever run.
|
|
1907
1905
|
- **Wait timer:** the run waits out the timer and resumes automatically when it elapses.
|
|
1908
1906
|
|
|
1909
|
-
A `reject` protection outcome (for example a disabled
|
|
1907
|
+
A `reject` protection outcome (for example a disabled context or a branch the context forbids) still fails the dispatch loudly with a clear reason, exactly as before — the orchestrator never dispatches a run with an unresolved install token.
|
|
1910
1908
|
|
|
1911
1909
|
## Limitations
|
|
1912
1910
|
|
|
@@ -1917,19 +1915,19 @@ A `reject` protection outcome (for example a disabled environment or a branch th
|
|
|
1917
1915
|
|
|
1918
1916
|
The orchestrator exposes Prometheus counters and a histogram under the `kici_orch_install_secrets_*` prefix on its `/metrics` endpoint. They populate the **Install secrets resolution** Grafana dashboard and let operators graph install-secrets activity without digging through Loki.
|
|
1919
1917
|
|
|
1920
|
-
| Metric | Type | Labels | What it tells you
|
|
1921
|
-
| ------------------------------------------------------------- | --------- | ------------------------------ |
|
|
1922
|
-
| `kici_orch_install_secrets_decisions_total` | Counter | `decision`, `reason` | Pass / reject / hold volume. `decision=hold` (reason `held`) counts dispatches paused at a reviewer-gated install
|
|
1923
|
-
| `kici_orch_install_secrets_npm_registry_used_total` | Counter | `channel`, `provider`, `scope` | Per-channel + per-scope usage. `channel=registries` is Option A, `channel=install_env` is Option C. `scope=default` marks a no-scope default registry; `scope=-` marks Option C entries.
|
|
1924
|
-
| `kici_orch_install_secrets_contributor_stripped_total` | Counter | `trust_tier` | Number of dispatches where registry tokens were stripped because the contributor tier wasn't `trusted` (fork PRs from unknown / known contributors). Expected to be 0 in single-tenant orgs.
|
|
1925
|
-
| `kici_orch_install_secrets_token_resolution_duration_seconds` | Histogram | `environment` | Latency of per-environment secret resolution. Pathological tails (>500ms) usually mean a Vault timeout or a slow Postgres replica.
|
|
1918
|
+
| Metric | Type | Labels | What it tells you |
|
|
1919
|
+
| ------------------------------------------------------------- | --------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1920
|
+
| `kici_orch_install_secrets_decisions_total` | Counter | `decision`, `reason` | Pass / reject / hold volume. `decision=hold` (reason `held`) counts dispatches paused at a reviewer-gated install context. Reject reasons enumerate the failure mode: `malformed_ref`, `invalid_url_scheme`, `env_not_found`, `protection_rule_block`, `missing_token`, `missing_install_env`, etc. |
|
|
1921
|
+
| `kici_orch_install_secrets_npm_registry_used_total` | Counter | `channel`, `provider`, `scope` | Per-channel + per-scope usage. `channel=registries` is Option A, `channel=install_env` is Option C. `scope=default` marks a no-scope default registry; `scope=-` marks Option C entries. |
|
|
1922
|
+
| `kici_orch_install_secrets_contributor_stripped_total` | Counter | `trust_tier` | Number of dispatches where registry tokens were stripped because the contributor tier wasn't `trusted` (fork PRs from unknown / known contributors). Expected to be 0 in single-tenant orgs. |
|
|
1923
|
+
| `kici_orch_install_secrets_token_resolution_duration_seconds` | Histogram | `environment` | Latency of per-environment secret resolution. Pathological tails (>500ms) usually mean a Vault timeout or a slow Postgres replica. |
|
|
1926
1924
|
|
|
1927
1925
|
The dashboard JSON lives at `infra/terraform/modules/grafana/dashboards/install-secrets.json`; if you maintain your own monitoring stack, you can import it directly.
|
|
1928
1926
|
|
|
1929
1927
|
## See also
|
|
1930
1928
|
|
|
1931
|
-
- [Secrets](https://docs.kici.dev/user/secrets/) — how to seed the `<
|
|
1932
|
-
- [
|
|
1929
|
+
- [Secrets](https://docs.kici.dev/user/secrets/) — how to seed the `<context>:<secret-name>` values referenced by `tokenSecret` / `installEnv`.
|
|
1930
|
+
- [Contexts](https://docs.kici.dev/user/contexts/) — protection rules (`branch_restrictions`, `requires_review`, `minimum_trust`) that the install gate inherits.
|
|
1933
1931
|
- [Operator: `kici-admin org-settings`](https://docs.kici.dev/operator/kici-admin-cli#org-settings----org-level-security-policy) — the `allow_http_npm_registries` toggle and other org-scoped knobs.
|
|
1934
1932
|
|
|
1935
1933
|
---
|
|
@@ -2198,11 +2196,11 @@ KiCI provides an explicit secrets API that gives workflow steps controlled acces
|
|
|
2198
2196
|
|
|
2199
2197
|
## Overview
|
|
2200
2198
|
|
|
2201
|
-
Secrets are managed per-
|
|
2199
|
+
Secrets are managed per-context in the orchestrator (see [operator docs](https://docs.kici.dev/operator/orchestrator/configuration) for setup). When a job runs with a `context` binding, the agent receives the secret keys available for that context but does **not** inject their values into the step's process environment. Instead, steps access secrets through the `ctx.secrets` API.
|
|
2202
2200
|
|
|
2203
2201
|
This design prevents accidental secret leakage through child processes, log output, or error messages. Only secrets you explicitly request are loaded into memory.
|
|
2204
2202
|
|
|
2205
|
-
A job can bind several
|
|
2203
|
+
A job can bind several contexts with `contexts: ['staging', 'my-testing']`; the secret keys from all bound contexts are merged in array order, with a later context's value winning on a key collision. See [Multiple contexts per job](https://docs.kici.dev/user/contexts/#multiple-contexts-per-job).
|
|
2206
2204
|
|
|
2207
2205
|
## Where secret values come from
|
|
2208
2206
|
|
|
@@ -2270,7 +2268,7 @@ export default workflow('deploy', {
|
|
|
2270
2268
|
jobs: [
|
|
2271
2269
|
job('deploy', {
|
|
2272
2270
|
runsOn: 'default',
|
|
2273
|
-
|
|
2271
|
+
context: 'production',
|
|
2274
2272
|
steps: [
|
|
2275
2273
|
step('deploy', async (ctx) => {
|
|
2276
2274
|
const token = await ctx.secrets.get('DEPLOY_TOKEN');
|
|
@@ -2394,7 +2392,7 @@ export default workflow('deploy', {
|
|
|
2394
2392
|
jobs: [
|
|
2395
2393
|
job('decrypt-and-deploy', {
|
|
2396
2394
|
runsOn: 'default',
|
|
2397
|
-
|
|
2395
|
+
context: 'production',
|
|
2398
2396
|
steps: [
|
|
2399
2397
|
step('decrypt', async (ctx) => {
|
|
2400
2398
|
const ageKeys = ctx.secrets.list().filter((k) => k.startsWith('AGE_KEY_'));
|
|
@@ -2426,7 +2424,7 @@ export default workflow('deploy', {
|
|
|
2426
2424
|
jobs: [
|
|
2427
2425
|
job('decrypt-and-deploy', {
|
|
2428
2426
|
runsOn: 'default',
|
|
2429
|
-
|
|
2427
|
+
context: 'production',
|
|
2430
2428
|
steps: [
|
|
2431
2429
|
step('decrypt-to-env', async (ctx) => {
|
|
2432
2430
|
await ctx.secrets.exposeFile('SOPS_AGE_KEY_FILE', {
|