@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.
Files changed (96) hide show
  1. package/dist/cli-banner.d.ts +6 -3
  2. package/dist/cli-banner.js +13 -5
  3. package/dist/cli.js +66 -43
  4. package/dist/commands/check-mode.d.ts +1 -1
  5. package/dist/commands/compile.js +3 -3
  6. package/dist/commands/index.d.ts +5 -1
  7. package/dist/commands/index.js +6 -3
  8. package/dist/commands/init.js +1 -1
  9. package/dist/commands/local-trust-root.d.ts +6 -0
  10. package/dist/commands/local-trust-root.js +52 -0
  11. package/dist/commands/local.d.ts +39 -0
  12. package/dist/commands/local.js +137 -0
  13. package/dist/commands/login.d.ts +2 -0
  14. package/dist/commands/login.js +40 -0
  15. package/dist/commands/logout.js +7 -0
  16. package/dist/commands/preview.js +1 -1
  17. package/dist/commands/run-banner.d.ts +31 -0
  18. package/dist/commands/run-banner.js +25 -0
  19. package/dist/commands/run-routed.d.ts +53 -0
  20. package/dist/commands/run-routed.js +176 -0
  21. package/dist/commands/run.d.ts +0 -17
  22. package/dist/commands/run.js +7 -50
  23. package/dist/commands/secrets-list.d.ts +2 -2
  24. package/dist/commands/secrets-list.js +4 -4
  25. package/dist/commands/types.d.ts +3 -3
  26. package/dist/commands/types.js +4 -4
  27. package/dist/execution/executor.js +7 -7
  28. package/dist/generators/secrets-dts.d.ts +9 -9
  29. package/dist/generators/secrets-dts.js +12 -12
  30. package/dist/llm-context/llms-architecture.txt +2 -2
  31. package/dist/llm-context/llms-cli.txt +674 -340
  32. package/dist/llm-context/llms-features.txt +334 -336
  33. package/dist/llm-context/llms-full.txt +1207 -892
  34. package/dist/llm-context/llms-getting-started.txt +7 -15
  35. package/dist/llm-context/llms-patterns.txt +5 -23
  36. package/dist/llm-context/llms-providers.txt +10 -1
  37. package/dist/llm-context/llms-sdk.txt +12 -12
  38. package/dist/llm-context/llms.txt +5 -5
  39. package/dist/local-executor/secret-loader.d.ts +1 -1
  40. package/dist/local-plane/orchestrator-process.d.ts +65 -0
  41. package/dist/local-plane/orchestrator-process.js +115 -0
  42. package/dist/local-plane/paths.d.ts +39 -0
  43. package/dist/local-plane/paths.js +54 -0
  44. package/dist/local-plane/plane-manager.d.ts +151 -0
  45. package/dist/local-plane/plane-manager.js +340 -0
  46. package/dist/local-plane/plane-seed.d.ts +76 -0
  47. package/dist/local-plane/plane-seed.js +59 -0
  48. package/dist/local-plane/plane-trigger.d.ts +62 -0
  49. package/dist/local-plane/plane-trigger.js +97 -0
  50. package/dist/local-plane/platform-attach.d.ts +53 -0
  51. package/dist/local-plane/platform-attach.js +111 -0
  52. package/dist/local-plane/postgres.d.ts +44 -0
  53. package/dist/local-plane/postgres.js +165 -0
  54. package/dist/local-plane/resolve-plane.d.ts +37 -0
  55. package/dist/local-plane/resolve-plane.js +76 -0
  56. package/dist/local-plane/run-follow.d.ts +41 -0
  57. package/dist/local-plane/run-follow.js +104 -0
  58. package/dist/local-plane/scaler-config.d.ts +54 -0
  59. package/dist/local-plane/scaler-config.js +114 -0
  60. package/dist/local-plane/secret-seed.d.ts +59 -0
  61. package/dist/local-plane/secret-seed.js +112 -0
  62. package/dist/local-plane/source-provider.d.ts +43 -0
  63. package/dist/local-plane/source-provider.js +165 -0
  64. package/dist/local-plane/trusted-routing.d.ts +32 -0
  65. package/dist/local-plane/trusted-routing.js +47 -0
  66. package/dist/lockfile/generator.js +7 -7
  67. package/dist/remote/dashboard-client.d.ts +7 -7
  68. package/dist/remote/dashboard-client.js +7 -7
  69. package/dist/remote/platform-client.d.ts +1 -1
  70. package/dist/remote/secret-loader.d.ts +18 -0
  71. package/dist/{local-executor → remote}/secret-loader.js +4 -4
  72. package/dist/remote/secret-upload.d.ts +1 -1
  73. package/dist/remote/secret-upload.js +2 -2
  74. package/dist/remote/uploader.d.ts +1 -1
  75. package/dist/remote/uploader.js +2 -2
  76. package/dist/templates/agents-md.d.ts +1 -1
  77. package/dist/templates/agents-md.js +2 -2
  78. package/dist/templates/package-json.js +1 -1
  79. package/dist/test-runner/job-executor.js +1 -1
  80. package/dist/test-runner/runs-on-display.d.ts +9 -0
  81. package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
  82. package/dist/test-runner/step-context.d.ts +1 -1
  83. package/dist/test-runner/step-context.js +2 -2
  84. package/dist/types.d.ts +6 -6
  85. package/package.json +7 -4
  86. package/sbom.spdx.json +9884 -1459
  87. package/dist/local-executor/dag-scheduler.js +0 -183
  88. package/dist/local-executor/index.js +0 -378
  89. package/dist/local-executor/job-runner.js +0 -411
  90. package/dist/local-executor/materializer.js +0 -132
  91. package/dist/local-executor/output-streamer.js +0 -168
  92. package/dist/local-executor/payload-generator.js +0 -138
  93. package/dist/local-executor/picker.js +0 -109
  94. package/dist/local-executor/to-event-payload.js +0 -21
  95. package/dist/local-executor/types.js +0 -2
  96. 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, environments, secrets, approvals, provenance, events.
3
+ This bundle covers: Workflow features: concurrency, contexts, secrets, approvals, provenance, events.
4
4
 
5
5
  ## Account and sign-in
6
6
 
@@ -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 `environments:write` or `ci_trust:write` permission. Use it when you want a manual gate without restricting who may release it.
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 environment via required reviewers (see [Environments](https://docs.kici.dev/user/environments/#required-reviewers)). When both apply to the same job, all clauses from both sources must be satisfied before the job is released. The two funnel into one held-element mechanism, so the dashboard queue and `kici approve` work the same way regardless of which source held the element.
231
+ `approval` is the **explicit** gate — a deliberate "pause for a human here" written by the workflow author. It composes with the **mandatory** gate an operator can attach to a protected context via required reviewers (see [Contexts](https://docs.kici.dev/user/contexts/#required-reviewers)). When both apply to the same job, all clauses from both sources must be satisfied before the job is released. The two funnel into one held-element mechanism, so the dashboard queue and `kici approve` work the same way regardless of which source held the element.
232
232
 
233
233
  ## Approving from the CLI
234
234
 
@@ -262,12 +262,14 @@ kici run remote deploy-prod --approve-all
262
262
 
263
263
  `--approve-all` is **run-scoped** — it only auto-approves holds belonging to the run this invocation dispatched; there is no fleet-wide or account-wide auto-approve. Eligibility is still enforced per hold: if you are not eligible for a gate, that gate still blocks. Each auto-approved gate prints its payload before resolving and is recorded distinctly in the audit trail (`held_run.auto_approve`).
264
264
 
265
- You can also approve from the dashboard approval queue. See [Dashboard](https://docs.kici.dev/user/dashboard/environments-and-secrets/#approval-queue).
265
+ `--approve-all` is honored in non-interactive runs too: with `--json` or `--quiet` (no TTY), the flag still auto-approves each eligible gate as it appears instead of hanging or printing out-of-band instructions. Hold notices are routed to stderr so `--json` stdout stays pure machine-readable output.
266
+
267
+ You can also approve from the dashboard approval queue. See [Dashboard](https://docs.kici.dev/user/dashboard/contexts-and-secrets/#approval-queue).
266
268
 
267
269
  ## See also
268
270
 
269
271
  - [Idempotent steps](https://docs.kici.dev/user/idempotent-steps/) — the check/apply step facet that drift gates build on.
270
- - [Environments](https://docs.kici.dev/user/environments/) — operator-required reviewers on protected environments.
272
+ - [Contexts](https://docs.kici.dev/user/contexts/) — operator-required reviewers on protected contexts.
271
273
  - [Approval gates (operator guide)](https://docs.kici.dev/operator/approvals/) — teams, the approval queue, expiry, and self-approval.
272
274
  - [Approval gates (architecture)](https://docs.kici.dev/architecture/approvals/) — the unified hold model and the step-level round-trip.
273
275
 
@@ -329,7 +331,7 @@ group: () => 'deploy';
329
331
  group: (ctx) => `deploy-${ctx.event.targetBranch ?? 'default'}`;
330
332
  ```
331
333
 
332
- The workflow-level group function is always evaluated **agent-side** at runtime -- the lock file records only that a group function exists (`hasGroup: true`), not the function itself. The agent loads the workflow source, calls the group function with `{ branch, event }`, and reports the evaluated key back to the orchestrator before step execution begins. This differs from job-level `concurrencyGroup` (see [Environments](https://docs.kici.dev/user/environments/#concurrency-groups)), where the compiler performs purity analysis and can inline pure functions for orchestrator-side evaluation.
334
+ The workflow-level group function is always evaluated **agent-side** at runtime -- the lock file records only that a group function exists (`hasGroup: true`), not the function itself. The agent loads the workflow source, calls the group function with `{ branch, event }`, and reports the evaluated key back to the orchestrator before step execution begins. This differs from job-level `concurrencyGroup` (see [Contexts](https://docs.kici.dev/user/contexts/#concurrency-groups)), where the compiler performs purity analysis and can inline pure functions for orchestrator-side evaluation.
333
335
 
334
336
  ## cancelInProgress mode
335
337
 
@@ -422,7 +424,7 @@ workflow('deploy', {
422
424
  jobs: [
423
425
  job('deploy-staging', {
424
426
  runsOn: 'linux',
425
- environment: 'staging',
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 environment protection
469
+ ## Interaction with context protection
468
470
 
469
- When a workflow has both `concurrency` and `environment` protection rules:
471
+ When a workflow has both `concurrency` and `context` protection rules:
470
472
 
471
- 1. Environment protection gates (required reviewers, wait timer) apply first
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 [Environments — concurrency groups](https://docs.kici.dev/user/environments/#concurrency-groups) for details.
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
- ## Dashboard
502
-
503
- Source: https://docs.kici.dev/user/dashboard/
504
-
505
- The KiCI dashboard is the browser interface for monitoring workflow runs, inspecting jobs and logs, and managing your organization. It signs in via OIDC and talks to KiCI over its API.
506
-
507
- This guide is split across the following pages:
508
-
509
- | Page | Covers |
510
- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
511
- | [Getting started](https://docs.kici.dev/user/dashboard/getting-started/) | Onboarding checklist and your organizations list |
512
- | [Navigation and layout](https://docs.kici.dev/user/dashboard/navigation/) | Sidebar, mobile nav, theme, time display, shortcuts, error pages |
513
- | [Runs and logs](https://docs.kici.dev/user/dashboard/runs/) | Run list, run detail, the log viewer |
514
- | [Settings](https://docs.kici.dev/user/dashboard/settings/) | Members, roles, teams, keys, sources, billing, security, support access |
515
- | [Workflows, diagnostics, and orchestrators](https://docs.kici.dev/user/dashboard/workflows-and-diagnostics/) | Registered workflows, infra health, per-cluster views |
516
- | [Environments, secrets, and approvals](https://docs.kici.dev/user/dashboard/environments-and-secrets/) | Environments, secret scopes, approval queue |
517
- | [Activity and DLQ](https://docs.kici.dev/user/dashboard/activity-and-dlq/) | Forensic activity log and dead-letter queue |
518
- | [Account](https://docs.kici.dev/user/dashboard/account/) | Personal account settings |
519
-
520
- ---
521
-
522
- ## Dynamic values
523
-
524
- Source: https://docs.kici.dev/user/dynamic-values/
525
-
526
- Dynamic values let you compute `environment`, `env`, and `concurrencyGroup` at runtime based on the incoming event. Instead of hardcoding static strings, you pass a function that receives the normalized event envelope and returns the resolved value.
527
-
528
- ```typescript
529
- job('deploy', {
530
- runsOn: ['default'],
531
- environment: (event) => event.targetBranch,
532
- env: (event) => ({ BRANCH: event.targetBranch }),
533
- concurrencyGroup: (event) => `deploy-${event.targetBranch}`,
534
- steps: [
535
- /* ... */
536
- ],
537
- });
538
- ```
539
-
540
- ```typescript
541
- job('deploy', {
542
- runsOn: 'default',
543
- // One shape everywhere: branch on the normalized event type.
544
- environment: (event) => (event.type === 'pull_request' ? 'preview' : 'production'),
545
- steps: [
546
- /* ... */
547
- ],
548
- });
549
- ```
550
-
551
- ## How it works
552
-
553
- When you define a dynamic value as a function, the compiler analyzes it at compile time to determine whether it is **pure** (can be evaluated without cloning the repo or running an init job).
554
-
555
- ### Pure functions (inline evaluation)
556
-
557
- A pure function is one that:
558
-
559
- - Is synchronous (no `async`/`await`)
560
- - Only references its parameters and local variables
561
- - Does not import or require external modules
562
- - Does not access globals like `process`, `fetch`, `console`, `setTimeout`, etc.
563
- - Uses only safe built-in constructors: `String`, `Number`, `Boolean`, `Array`, `Object`, `JSON`, `Math`, `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `encodeURIComponent`, `decodeURIComponent`, `encodeURI`, `decodeURI`
564
- - Does not use `this`, `new`, `class`, `throw`, `try`/`catch`, `delete`, `var`, `yield`, or mutation operators (`++`, `--`, `+=`, etc.)
565
-
566
- When the compiler detects a pure function, it serializes the function source directly into the lock file as an inline expression. At dispatch time, the orchestrator evaluates the expression in a sandboxed VM context (~0ms overhead) instead of dispatching an init job.
567
-
568
- **Examples of pure functions:**
569
-
570
- ```typescript
571
- // Simple branch extraction
572
- environment: (event) => event.targetBranch;
573
-
574
- // Object literal with string operations
575
- env: (event) => ({ BRANCH: event.targetBranch });
576
-
577
- // Concatenation with event data
578
- concurrencyGroup: (event) => `deploy-${event.targetBranch}`;
579
-
580
- // Using safe globals
581
- env: (event) => ({ UPPER: String(event.targetBranch).toUpperCase() });
582
-
583
- // Local variables are fine
584
- environment: (event) => {
585
- const parts = event.targetBranch.split('/');
586
- return parts[parts.length - 1];
587
- };
588
- ```
589
-
590
- ### Impure functions (init-job evaluation)
591
-
592
- If the compiler determines a function is impure, it emits a warning during compilation and falls back to the two-phase init model. This means:
593
-
594
- 1. The orchestrator dispatches a special `__init__` job to a builder agent
595
- 2. The builder agent clones the repository and evaluates the function
596
- 3. The resolved values are sent back to the orchestrator
597
- 4. The orchestrator dispatches the real execution job with the resolved values
598
-
599
- This adds approximately 5-10 seconds of overhead for cloning and evaluation.
600
-
601
- **Examples of impure functions (will use init job):**
602
-
603
- ```typescript
604
- // Async functions cannot be inlined
605
- environment: async (event) => await lookupEnv(event.targetBranch);
606
-
607
- // External module references
608
- env: (event) => {
609
- const config = require('./config');
610
- return config.env;
611
- };
612
-
613
- // Process/global access
614
- environment: (event) => process.env.DEFAULT_ENV || 'staging';
615
-
616
- // Dynamic imports
617
- env: async (event) => {
618
- const m = await import('./config.js');
619
- return m.default;
620
- };
621
- ```
622
-
623
- ## Performance comparison
624
-
625
- | Evaluation path | Overhead | When used |
626
- | ------------------------------------ | -------- | ------------------------------------------------------------------- |
627
- | Static value (string/object literal) | ~0ms | `environment: 'staging'` |
628
- | Inline expression (pure function) | ~0ms | `environment: (event) => event.targetBranch` |
629
- | Init job (impure function) | ~5-10s | `environment: async (event) => await lookupEnv(event.targetBranch)` |
630
-
631
- ## Tips
632
-
633
- - **Write pure functions whenever possible** to avoid the init-job delay. Most environment and env computations only need the event payload data.
634
- - **Check compiler warnings** -- the compiler tells you when a function is classified as impure and explains why.
635
- - **Runtime errors in inline expressions cause immediate job failure.** There is no fallback to the init-job path. If your pure function throws at runtime (e.g., accessing a property on `undefined`), the job fails immediately.
636
- - **The event parameter is the normalized event envelope** — the same shape rules receive as `ctx.event`: `{ type, action, targetBranch, sourceBranch, changedFiles, payload, … }` (see the [event payload reference](https://docs.kici.dev/user/sdk/event-payloads/) for the complete schema). Narrow on `event.type` (`'push'`, `'pull_request'`, `'tag'`, …) to branch per trigger kind. The raw provider webhook body is nested at `event.payload` (for GitHub pushes: `payload.ref`, `payload.after`, `payload.repository`, …).
637
-
638
- ---
639
-
640
- ## Environment variables
641
-
642
- Source: https://docs.kici.dev/user/env-vars/
643
-
644
- The KiCI CLI reads the following environment variables to customize its behavior. OAuth login (`kici login` without `--token`) defaults `KICI_PLATFORM_URL`, `KICI_OIDC_ISSUER`, and `KICI_OIDC_CLIENT_ID` to the hosted KiCI Platform, so `kici login` works with no configuration. Set them only to target another KiCI environment (e.g. a testing instance) or a custom OIDC provider.
645
-
646
- ## Authentication
647
-
648
- | Variable | Description | Default |
649
- | --------------------- | -------------------------------------- | -------------------------------------------- |
650
- | `KICI_OIDC_ISSUER` | OIDC issuer URL for authentication | `https://auth.kici.dev/realms/kici-internal` |
651
- | `KICI_OIDC_CLIENT_ID` | OIDC client ID for the CLI application | `kici-cli` |
652
- | `KICI_PLATFORM_URL` | Platform API base URL | `https://api.kici.dev` |
653
- | `KICI_CONFIG_DIR` | Override the KiCI config directory | `~/.kici` |
654
-
655
- ## Browser behavior
656
-
657
- | Variable | Description | Default |
658
- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
659
- | `KICI_BROWSER_CMD` | Custom browser command for OAuth login. Supports `{url}` placeholder. Set to `none` to suppress browser opening and print the URL to stdout instead. | Uses the system default browser via the `open` package |
660
- | `KICI_CALLBACK_PORT` | Fixed port for the OAuth PKCE callback server. Useful when firewall rules require a known port. | Random available port |
661
-
662
- ## Development
663
-
664
- | Variable | Description | Default |
665
- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
666
- | `KICI_DEV` | Enable development mode. When `true`, uses prerelease-compatible version ranges (`>=0.0.1-0`) for dev dependencies and skips npm version resolution. | unset |
667
- | `KICI_DEBUG` | Enable debug logging. When `true`, prints verbose diagnostics (SDK alias resolution, step-level debug logs, stack traces on errors). Equivalent to the `--debug` CLI flag. | unset |
668
-
669
- ## Usage examples
670
-
671
- ### CI/CD environment
672
-
673
- Authenticate with a pre-existing API key (no browser needed):
674
-
675
- ```bash
676
- kici login --token <<< "$KICI_API_KEY"
677
- ```
678
-
679
- ### Targeting another environment or custom OIDC provider
680
-
681
- `kici login` targets the hosted KiCI Platform by default. To point the CLI at another KiCI environment (e.g. a testing instance) or a custom OIDC provider, override the defaults:
682
-
683
- ```bash
684
- export KICI_OIDC_ISSUER=https://your-idp.example.com
685
- export KICI_OIDC_CLIENT_ID=your-client-id
686
- export KICI_PLATFORM_URL=https://your-platform.example.com
687
- kici login
688
- ```
689
-
690
- ### Headless SSH session
691
-
692
- The CLI auto-detects headless environments and uses the device flow. To force PKCE with URL output instead:
693
-
694
- ```bash
695
- export KICI_BROWSER_CMD=none
696
- kici login
697
- ```
698
-
699
- This prints the authorization URL to stdout as `KICI_AUTH_URL=<url>`. Open the URL in any browser to complete authentication.
700
-
701
- ### Fixed callback port
702
-
703
- When behind a firewall or using port forwarding:
704
-
705
- ```bash
706
- export KICI_CALLBACK_PORT=19876
707
- kici login
708
- ```
709
-
710
- ### Custom config location
711
-
712
- Store the KiCI config in a non-default location:
713
-
714
- ```bash
715
- export KICI_CONFIG_DIR=/tmp/kici-test
716
- kici login
717
- ```
718
-
719
- ---
720
-
721
- ## Environments
501
+ ## Contexts
722
502
 
723
- Source: https://docs.kici.dev/user/environments/
503
+ Source: https://docs.kici.dev/user/contexts/
724
504
 
725
- <!-- help:environments-list#overview -->
505
+ <!-- help:contexts-list#overview -->
726
506
 
727
- Environments are named deployment targets (like staging or production) that control where your workflow jobs run. Each environment can have its own variables, secrets, and protection rules to gate deployments.
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:environments-list -->
509
+ <!-- /help:contexts-list -->
730
510
 
731
- <!-- help:environments-protection#protection-rules -->
511
+ <!-- help:contexts-protection#protection-rules -->
732
512
 
733
- Protection rules control when jobs targeting an environment can execute.
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:environments-protection -->
522
+ <!-- /help:contexts-protection -->
743
523
 
744
- Environments represent deployment targets like `staging`, `production`, or `review/PR-*`. Each environment can have its own variables, bound secrets, and protection rules that control when and how jobs targeting that environment can execute.
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
- An environment in KiCI provides:
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 environment via scope bindings
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 environment property
537
+ ### Job-level context property
758
538
 
759
- The `environment` property is set on a job, not a workflow or step:
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
- environment: 'staging',
549
+ context: 'staging',
770
550
  steps: [
771
551
  step('deploy', async (ctx) => {
772
- // ctx.environment is the resolved environment name
773
- console.log(`Deploying to ${ctx.environment}`);
774
- // ctx.secrets provides async get/expose/has methods for environment-bound secrets
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.environment}`;
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 environments
566
+ ### Dynamic contexts
787
567
 
788
- The environment name can be a string or a function (sync or async) for dynamic environments (e.g., per-PR review environments). The function receives the normalized event envelope, with the raw provider body nested at `event.payload`:
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
- environment: (event) => `review/PR-${event.payload.pull_request.number}`,
573
+ context: (event) => `review/PR-${event.payload.pull_request.number}`,
794
574
  steps: [
795
575
  step('deploy', async (ctx) => {
796
- // ctx.environment is 'review/PR-123' (resolved at runtime)
797
- await ctx.$`deploy-preview --env ${ctx.environment}`;
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 environments that match a glob pattern (e.g., `review/*`) inherit the pattern's configuration, variables, and protection rules.
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 environments per job
585
+ ### Multiple contexts per job
806
586
 
807
- A job can bind more than one environment with `environments`, an ordered array. This lets a single job draw secrets and variables from several environments at once — for example a shared `staging` environment plus a `my-testing` environment that carries test-only variables:
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
- environments: ['staging', 'my-testing'],
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 environments
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
- - `environment` (singular) and `environments` (array) are mutually exclusive — setting both is a compile error. `environment: 'staging'` is exactly equivalent to `environments: ['staging']`.
823
- - Each array entry is a static name or a function of the event, resolved per element exactly like a single dynamic environment.
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 environments are resolved on every dispatch (webhook, scheduled, and test runs alike) and merged in array order. When the same secret or variable key is defined in more than one environment, the later entry in the array wins. With `environments: ['staging', 'my-testing']`, a key defined in both resolves to `my-testing`'s value; keys defined in only one are preserved. The longest-scope-path-wins rule still applies _within_ each environment.
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 environment's gates — adding an environment can never loosen access. Branch restrictions, trigger-type filters, and repo patterns must pass for all environments; the minimum trust tier is the most restrictive across them; required reviewers are the union of all environments' reviewers; the wait timer is the longest; and the hold expiry is the shortest. If a run is gated out, the rejection names which environment and which rule rejected it (visible via `kici runs show <run-id>` and the run's rejection reason), so a mutually-exclusive set of rules surfaces as a clear failure rather than a silent perpetual rejection.
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 environment never rejects the run. Any bound environment that disallows local execution (`allowLocalExecution: false`) — or that is not configured — is **skipped**: its variables and secrets are omitted from the merge and its gates are not evaluated. The run proceeds, and a user-visible warning naming the skipped environment(s) is shown both on the `kici run remote` CLI output and on the dashboard run view. This makes the test-only-variables pattern work: with `environments: ['staging', 'my-testing']` where only `my-testing` allows local execution, a test run resolves just `my-testing`'s variables and warns that `staging` was skipped. If every bound environment is skipped, the job runs with no environment variables. This is intentionally different from a fixture `secrets:` mapping, which is fail-closed — see the [testing guide](https://docs.kici.dev/user/testing-guide/).
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 environments contribute nothing at dispatch.** At dispatch time a bound environment name with no matching configured environment (and no matching glob environment) simply adds no variables, secrets, or protection rules — the job still runs, exactly as a single dynamic environment resolving to an as-yet-unconfigured name does today.
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-environment binding: a bound environment that does not exist, a disabled one, or two environments with mutually-exclusive fixed branch / trigger-type / repository restrictions (no value can satisfy both) makes the binding provably unsatisfiable, and the registration is rejected with a precise message naming the job, the environments, and the rule — for example `unsatisfiable environment binding: job 'deploy' binds environments [staging, my-testing] with mutually exclusive branch restrictions (no value satisfies all bound environments)`. Bindings whose restrictions use globs are undecidable at registration and fall through to the dispatch-time gate check instead.
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
- environment: 'production',
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 an environment. For workflow-level concurrency (which applies to all jobs in a workflow), see [Concurrency groups](https://docs.kici.dev/user/concurrency/).
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 environment:
639
+ Control concurrent deployments to the same context:
860
640
 
861
641
  ```typescript
862
642
  job('deploy', {
863
643
  runsOn: 'default',
864
- environment: 'production',
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 environment name is used as the default concurrency group. For a job bound to multiple environments, the default is the **first** bound environment's name.
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 | Type | Description |
881
- | ----------------- | ------------------------------------- | ----------------------------------------------------------------------------------------- |
882
- | `ctx.environment` | `string \| undefined` | Resolved environment name (undefined for jobs without environment) |
883
- | `ctx.env` | `Record<string, string \| undefined>` | Environment variables (merged from system, org, source, and job-level `env`) |
884
- | `ctx.secrets` | `StepSecretsTyped` | Async accessor for bound secrets (get, expose, has, getMeta, list, mountFile, exposeFile) |
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 an environment, variables are merged in this order (last wins):
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 environment vars** -- from the dashboard, managed per-environment
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/environments/#step-context) section above.
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
- Environments can have protection rules that gate job execution:
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 an environment:
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 environment-level approval queues.
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 an environment:
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 environments
779
+ ### Creating contexts
1000
780
 
1001
- Navigate to **Settings > Environments** in the dashboard. Click **New environment** to choose the environment name and type (Fixed or Glob).
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 environment name, like `staging` or `production`
1004
- - **Glob** -- applies to any environment name a job declares that matches the pattern, e.g. `review/*` matches a job with `environment: 'review/PR-123'`
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 environments list shows each environment's type, whether test runs may use it (the `allowLocalExecution` flag -- see the [testing guide](https://docs.kici.dev/user/testing-guide/)), and whether it is enabled.
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
- ### Environment detail page
788
+ ### Context detail page
1009
789
 
1010
- Each environment has four tabs:
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 environment.
798
+ 4. **History** -- view filtered runs targeting this context.
1019
799
 
1020
- ### Bound environments on runs
800
+ ### Bound contexts on runs
1021
801
 
1022
- A job's bound deployment environments are shown as chips on the run detail page (in the job metadata panel) in the order the job declared them, and the distinct set across a run's jobs appears as compact chips on the run list. For a multi-environment job the chips read left-to-right in merge order — later environments override earlier ones on key collisions. A `(dynamic)` chip marks an environment whose name is computed at runtime; it resolves to the real name once the run starts. A job that binds a single environment shows one chip; a job that binds none shows no chip.
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-environment binding is gated out, the run's failure banner names which environment and which rule rejected it (the same all-must-pass detail surfaced by `kici runs show <run-id>`).
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 environments via bindings:
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 environment binding checkboxes
1031
- - **Environment-centric view** (inside environment detail): bound scopes, resolved secrets, add binding
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 environments) and `EnvironmentSecrets` (per-environment key unions):
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 EnvironmentSecrets {
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. `EnvironmentSecrets` maps each environment to its available secret key names as a string union. Dynamic environments fall back to the full `KnownSecretKeys` union.
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` work on both local and remote runs:
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 push
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: exit non-zero (2) locally, or fail the run remotely,
1658
- # when any step reports drift. Use this as a CI gate ("fail the build if prod
1659
- # has drifted").
1660
- kici run local push --check --fail-on-drift
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 `<environment>:<secret-name>` syntax. The orchestrator resolves the token at dispatch time and the agent applies it for one `npm install` only.
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
- environment: 'production',
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 `<environment>:<secret-name>`. The orchestrator looks up the secret in the named environment via the per-environment secret resolver. The bare name **must not** contain a colon.
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
- environment: 'production',
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` environment's secret store and seeds it as `MY_NPM_TOKEN` (bare name) in the install subprocess. Your committed `.npmrc` reads it through `${MY_NPM_TOKEN}`.
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
- environment: 'production',
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
- environment: 'production',
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` environment.
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-environment scoping.** Every `tokenSecret` and `installEnv` entry is qualified with an environment name. The orchestrator runs the same protection-rule pipeline (branch / trust / concurrency / reviewer / wait-timer) against each named environment **before** resolving any secret, so a workflow that wants a `production` token from a feature branch is rejected exactly like a job that tries to deploy to `production` from a feature branch. A reviewer-gated install environment **pauses** the whole workflow dispatch as a workflow-scoped held run instead of resolving the token — see [Reviewer-gated installs](https://docs.kici.dev/user/private-registries/#reviewer-gated-installs) below.
1896
- - **Untrusted contributors get no tokens.** When a fork PR is dispatched and the contributor-trust resolution returns anything other than `trusted`, the orchestrator strips both `npmRegistries` and `installEnvSecrets` out of the dispatch. The install runs without auth and fails naturally on the first private dep — fork PRs cannot ever observe a registry token, even if a misconfigured environment lacks an explicit `requiredTrustTier`.
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 environment carries a protection rule that holds — a required reviewer (`hold`) or a wait timer (`wait`) — the install gate **pauses the whole workflow dispatch** instead of rejecting it. The run is created in the `held` state, no jobs are queued, and a workflow-scoped row appears on the held-runs page with a `Workflow` scope badge.
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 environment or a branch the environment forbids) still fails the dispatch loudly with a clear reason, exactly as before — the orchestrator never dispatches a run with an unresolved install token.
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 environment. Reject reasons enumerate the failure mode: `malformed_ref`, `invalid_url_scheme`, `env_not_found`, `protection_rule_block`, `missing_token`, `missing_install_env`, etc. |
1923
- | `kici_orch_install_secrets_npm_registry_used_total` | Counter | `channel`, `provider`, `scope` | Per-channel + per-scope usage. `channel=registries` is Option A, `channel=install_env` is Option C. `scope=default` marks a no-scope default registry; `scope=-` marks Option C entries. |
1924
- | `kici_orch_install_secrets_contributor_stripped_total` | Counter | `trust_tier` | Number of dispatches where registry tokens were stripped because the contributor tier wasn't `trusted` (fork PRs from unknown / known contributors). Expected to be 0 in single-tenant orgs. |
1925
- | `kici_orch_install_secrets_token_resolution_duration_seconds` | Histogram | `environment` | Latency of per-environment secret resolution. Pathological tails (>500ms) usually mean a Vault timeout or a slow Postgres replica. |
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 `<environment>:<secret-name>` values referenced by `tokenSecret` / `installEnv`.
1932
- - [Environments](https://docs.kici.dev/user/environments/) — protection rules (`branch_restrictions`, `requires_review`, `minimum_trust`) that the install gate inherits.
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-environment in the orchestrator (see [operator docs](https://docs.kici.dev/operator/orchestrator/configuration) for setup). When a job runs with an `environment` binding, the agent receives the secret keys available for that environment but does **not** inject their values into the step's process environment. Instead, steps access secrets through the `ctx.secrets` API.
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 environments with `environments: ['staging', 'my-testing']`; the secret keys from all bound environments are merged in array order, with a later environment's value winning on a key collision. See [Multiple environments per job](https://docs.kici.dev/user/environments/#multiple-environments-per-job).
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
- environment: 'production',
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
- environment: 'production',
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
- environment: 'production',
2427
+ context: 'production',
2430
2428
  steps: [
2431
2429
  step('decrypt-to-env', async (ctx) => {
2432
2430
  await ctx.secrets.exposeFile('SOPS_AGE_KEY_FILE', {