@kici-dev/compiler 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/cli.js +37 -7
  2. package/dist/commands/approve.d.ts +12 -0
  3. package/dist/commands/approve.js +5 -2
  4. package/dist/commands/compile.js +4 -2
  5. package/dist/commands/doctor.js +2 -2
  6. package/dist/commands/endpoints.js +4 -6
  7. package/dist/commands/held-run-client.d.ts +21 -1
  8. package/dist/commands/held-run-client.js +34 -15
  9. package/dist/commands/hook.js +22 -20
  10. package/dist/commands/index.d.ts +2 -0
  11. package/dist/commands/index.js +2 -1
  12. package/dist/commands/init.d.ts +9 -2
  13. package/dist/commands/init.js +43 -16
  14. package/dist/commands/login.js +1 -1
  15. package/dist/commands/orchestrators.js +3 -2
  16. package/dist/commands/reject.d.ts +12 -0
  17. package/dist/commands/reject.js +5 -2
  18. package/dist/commands/report/collect.d.ts +82 -0
  19. package/dist/commands/report/collect.js +234 -0
  20. package/dist/commands/report/identity.d.ts +48 -0
  21. package/dist/commands/report/identity.js +49 -0
  22. package/dist/commands/report/index.d.ts +63 -0
  23. package/dist/commands/report/index.js +119 -0
  24. package/dist/commands/report/upload.d.ts +38 -0
  25. package/dist/commands/report/upload.js +64 -0
  26. package/dist/commands/run-hold-watch.js +2 -2
  27. package/dist/commands/run.js +6 -3
  28. package/dist/commands/runs/show.js +80 -1
  29. package/dist/commands/types.js +51 -8
  30. package/dist/execution/sdk-alias.js +4 -2
  31. package/dist/fixtures/compiler.js +2 -1
  32. package/dist/format.js +3 -3
  33. package/dist/generators/secrets-dts.d.ts +8 -2
  34. package/dist/generators/secrets-dts.js +3 -2
  35. package/dist/hooks/installer.js +2 -1
  36. package/dist/llm-context/llms-architecture.txt +35 -13
  37. package/dist/llm-context/llms-cli.txt +142 -28
  38. package/dist/llm-context/llms-features-execution.txt +2017 -0
  39. package/dist/llm-context/llms-features.txt +298 -1483
  40. package/dist/llm-context/llms-full.txt +3008 -1698
  41. package/dist/llm-context/llms-getting-started.txt +145 -12
  42. package/dist/llm-context/llms-patterns.txt +176 -1
  43. package/dist/llm-context/llms-providers.txt +11 -27
  44. package/dist/llm-context/llms-sdk-runtime.txt +25 -4
  45. package/dist/llm-context/llms-sdk.txt +31 -1
  46. package/dist/llm-context/llms.txt +22 -14
  47. package/dist/local-plane/paths.d.ts +15 -0
  48. package/dist/local-plane/paths.js +22 -1
  49. package/dist/local-plane/plane-manager.js +2 -2
  50. package/dist/local-plane/port-holder.js +1 -1
  51. package/dist/local-plane/postgres.d.ts +3 -16
  52. package/dist/local-plane/postgres.js +10 -15
  53. package/dist/lockfile/generator.d.ts +12 -0
  54. package/dist/lockfile/generator.js +47 -14
  55. package/dist/postinstall.js +2 -1
  56. package/dist/remote/config.d.ts +2 -15
  57. package/dist/remote/config.js +2 -16
  58. package/dist/remote/dashboard-client.d.ts +39 -0
  59. package/dist/remote/dashboard-client.js +41 -0
  60. package/dist/remote/oauth.js +7 -5
  61. package/dist/remote/uploader.js +2 -2
  62. package/dist/templates/package-json.js +1 -1
  63. package/dist/test-runner/dry-run.js +4 -2
  64. package/dist/test-runner/git-detector.js +2 -1
  65. package/dist/test-runner/job-executor.js +2 -1
  66. package/dist/test-runner/payload-builder.js +11 -17
  67. package/dist/types.d.ts +33 -3
  68. package/dist/validation/validator.js +23 -6
  69. package/package.json +16 -11
  70. package/sbom.spdx.json +953 -901
@@ -76,6 +76,8 @@ Trigger matching can query the **contents** of individual source files, not just
76
76
 
77
77
  The orchestrator also does **not** run `dynamicJob` generator bodies itself: for the event-only (function) form it dispatches a dedicated dynamic-evaluation job to an agent at event time; the generator function then runs agent-side (see below).
78
78
 
79
+ A workflow-level `filter` predicate is the same shape: the orchestrator sees only the lock's `hasFilter` flag, never the predicate, so it dispatches an evaluation job and lets an agent decide whether the workflow applies. Prefer the declarative filters where they answer the question — `commitMessage` on the trigger and `requires` over source files cost no evaluation job at all.
80
+
79
81
  See [dynamic values](https://docs.kici.dev/user/dynamic-values/) for how dynamic `context`, `env`, and `concurrencyGroup` functions resolve.
80
82
 
81
83
  ## Agent time
@@ -86,20 +88,33 @@ After dispatch, each job runs in its own ephemeral agent sandbox: a shallow clon
86
88
  2. **Step-level rules**, then each step's `run()` body and its hooks.
87
89
  3. **Dynamic values** (`context`, `env`, `concurrencyGroup` functions) are resolved here, via a short `__init__` job that runs the function before the real job runs; this shows in the run timeline as an `Init:` entry.
88
90
  4. **`dynamicJob` generators run here — both forms.** The event-only (function) form runs in a dedicated evaluation job dispatched at event time; the result-aware (options) form is deferred until its declared `needs` complete, then run with the upstream outputs frozen as `ctx.needs`.
91
+ 5. **A workflow-level `filter` predicate runs here too**, before the jobs it gates. A global workflow evaluates it once per (event × workflow repo), before any run row exists, so a `false` verdict leaves no run at all. A same-repo workflow evaluates it once per job that reaches dispatch and once per job generator, after the run row exists, so a `false` verdict leaves a run whose only entries are the evaluation jobs. Keep the predicate cheap, pure, and side-effect free — a ten-job workflow calls it ten times for one event. See [narrowing with a filter](https://docs.kici.dev/user/global-workflows/#narrowing-with-a-filter).
89
92
 
90
93
  See [job execution](https://docs.kici.dev/architecture/execution/job-execution/) and [hooks and rules](https://docs.kici.dev/user/hooks/) for the details.
91
94
 
95
+ ## How a reused agent stays clean between jobs
96
+
97
+ An ephemeral agent is discarded after one job, so residue never matters. A **reused agent** — a long-lived process on a shared host (the bare-metal profile) — serves many jobs in turn. Between them, the agent runs a supervisor-owned cleanup phase so one job's leftovers never reach the next:
98
+
99
+ 1. **It reaps the finished job's process tree.** A step may background a daemon that outlives the job. The agent runs each job's process in its own process group and signals the whole group when the job ends, so a stray daemon does not survive into the next job. Set `KICI_AGENT_ORPHAN_CLEANUP=false` to keep only the runner and leave a backgrounded process alive on purpose.
100
+ 2. **It re-runs declared cleanup after a hard kill.** A job's `cleanup` / `onFailure` hooks normally run in the job process. If that process is killed hard (out of memory, forced stop), the agent re-runs the declared cleanup against the preserved work directory.
101
+ 3. **It deletes the work directory.**
102
+ 4. **It runs an optional operator reset command.** Set `KICI_AGENT_BETWEEN_JOBS_RESET_COMMAND` to a host-reset command (for example, pruning a container cache). It runs after the reap and work-directory deletion. A failure never fails the finished job.
103
+
104
+ This phase is the primary cross-job cleanup. The agent's startup temp-directory sweep stays as a backstop for anything a between-jobs phase missed. See [agent configuration](https://docs.kici.dev/operator/agent/configuration/) for the full env-var reference.
105
+
92
106
  ## What re-evaluates where
93
107
 
94
- | Construct | Runs on | When |
95
- | ---------------------------- | ---------------- | ------------------------------- |
96
- | Static value | Compile → lock | Never re-evaluated |
97
- | Dynamic value | Agent init step | Per event |
98
- | Job-level rules | Agent | After clone |
99
- | Step-level rules | Agent | Per step |
100
- | `dynamicJob` (function form) | Agent (eval job) | Dispatched at event time |
101
- | `dynamicJob` (options form) | Agent | Deferred until `needs` complete |
102
- | Step / job body + hooks | Agent | Per job |
108
+ | Construct | Runs on | When |
109
+ | ---------------------------- | ---------------- | ---------------------------------------------------- |
110
+ | Static value | Compile → lock | Never re-evaluated |
111
+ | Dynamic value | Agent init step | Per event |
112
+ | Job-level rules | Agent | After clone |
113
+ | Step-level rules | Agent | Per step |
114
+ | `dynamicJob` (function form) | Agent (eval job) | Dispatched at event time |
115
+ | `dynamicJob` (options form) | Agent | Deferred until `needs` complete |
116
+ | Workflow `filter` predicate | Agent (eval job) | Per event (global) / per job + generator (same-repo) |
117
+ | Step / job body + hooks | Agent | Per job |
103
118
 
104
119
  **Determinism note.** `ctx.event` and `ctx.needs` are frozen snapshots — captured once and replayed unchanged on any re-evaluation. A generator that derives its output from them is stable across re-evaluations; one that reads the wall clock (`Date.now()`) or a random source (`Math.random()`) is not.
105
120
 
@@ -149,6 +164,124 @@ Outputs are typed across the job boundary too: reading `jobRef.result.…` or `c
149
164
 
150
165
  ---
151
166
 
167
+ ## Getting help
168
+
169
+ Source: https://docs.kici.dev/user/getting-help/
170
+
171
+ When something goes wrong, the fastest path to a fix is a report that already
172
+ carries the context. This page covers what to try first, what to send, and how
173
+ to send it privately.
174
+
175
+ ## Diagnose it yourself first
176
+
177
+ Two commands answer most problems without anyone else involved:
178
+
179
+ ```bash
180
+ # Check your own setup: login, org, lock file, orchestrator, agent labels
181
+ kici doctor
182
+
183
+ # Look at the org's infrastructure: orchestrators, scalers, agents
184
+ kici diagnostics
185
+ ```
186
+
187
+ `kici doctor` prints the next command to run for each problem it finds. Work
188
+ down its output before reporting — a stale lock file or an expired login is a
189
+ one-command fix.
190
+
191
+ If your workflow ran and failed, read its logs:
192
+
193
+ ```bash
194
+ kici runs show <run-id>
195
+ kici runs logs <run-id>
196
+ ```
197
+
198
+ [Common failures](https://docs.kici.dev/user/common-failures/) covers the errors people hit most.
199
+
200
+ ## Report a problem
201
+
202
+ When you cannot resolve it yourself, gather a diagnostic bundle:
203
+
204
+ ```bash
205
+ kici report --run <run-id> --message "what you expected, and what happened"
206
+ ```
207
+
208
+ The command writes a ZIP and prints its path and `sha256`. It sends nothing.
209
+ The bundle holds:
210
+
211
+ - your CLI, Node, and orchestrator versions,
212
+ - your redacted KiCI configuration,
213
+ - your project's workflow list and lock-file state,
214
+ - the failing run's detail and logs, when you pass `--run`,
215
+ - a collection report saying which of those the command could and could not
216
+ read.
217
+
218
+ Open the file and read it. It is yours until you decide to share it.
219
+
220
+ ### What gets redacted
221
+
222
+ KiCI removes known secret shapes before anything enters the bundle:
223
+
224
+ - API keys and access tokens (AWS, GitHub, Slack, KiCI agent tokens),
225
+ - `Authorization` headers and JSON web tokens,
226
+ - passwords inside connection URLs,
227
+ - private keys and encrypted-value blocks,
228
+ - values assigned to a secret-named key, such as `api_key=` or `password=`.
229
+
230
+ Configuration is redacted twice: an allowlist keeps only known-safe fields, and
231
+ the free-text scrubber runs over what remains.
232
+
233
+ **Redaction is best effort.** A secret in a format KiCI does not recognize can
234
+ survive it. Review the bundle before you share it. `--no-redact` turns
235
+ redaction off and prints a warning — use it only for a bundle you keep.
236
+
237
+ ## Send it privately
238
+
239
+ Reports contain your data, so there is no public tracker for them. Add
240
+ `--upload` to send the bundle to KiCI directly:
241
+
242
+ ```bash
243
+ kici report --run <run-id> --upload --message "matrix job hangs on macOS"
244
+ ```
245
+
246
+ The command prints a reference id. Quote it in any conversation about the
247
+ problem. The bundle goes straight from your machine to KiCI storage over a
248
+ one-time upload link — it never passes through the dashboard.
249
+
250
+ Add `--email` if you want a reply address attached to the report.
251
+
252
+ ## Manage what you have sent
253
+
254
+ An upload is not permanent, and it is yours to revoke:
255
+
256
+ ```bash
257
+ # See the reports you have uploaded
258
+ kici report list
259
+
260
+ # Delete an uploaded bundle
261
+ kici report withdraw <ref>
262
+ ```
263
+
264
+ Uploaded bundles expire automatically after 90 days.
265
+
266
+ Anyone in your organization can upload a report. By default you see and
267
+ withdraw your own. A member with the `support:admin` permission can manage
268
+ every report in the organization, which is how an owner cleans up on behalf of
269
+ someone who has left.
270
+
271
+ ## Reporting a security issue
272
+
273
+ Do not use `kici report` for a suspected vulnerability. Follow the disclosure
274
+ process in [SECURITY.md](https://github.com/kici-dev/kici/blob/master/SECURITY.md)
275
+ instead.
276
+
277
+ ## See also
278
+
279
+ - [Common failures](https://docs.kici.dev/user/common-failures/) — the errors people hit most, and their fixes
280
+ - [CLI reference](https://docs.kici.dev/user/cli-reference/) — every `kici` command
281
+ - [Dashboard](https://docs.kici.dev/user/dashboard/) — inspecting runs in the browser
282
+
283
+ ---
284
+
152
285
  ## Getting started with workflows
153
286
 
154
287
  Source: https://docs.kici.dev/user/getting-started/
@@ -171,11 +304,11 @@ npx kici init
171
304
 
172
305
  This will:
173
306
 
174
- 1. Create `.kici/` directory with `workflows/`, `tests/`, `types/`, `package.json`, and `tsconfig.json`
307
+ 1. Create `.kici/` directory with `workflows/`, `tests/`, `types/`, `package.json`, and `tsconfig.json`. The `types/` folder holds a local development aid — TypeScript declarations that `kici types` (and an authenticated `kici compile`) generate from your orchestrator's secret contexts. Its content is a snapshot of one org's secret keys, so it is not committed.
175
308
  2. Create a `.kiciignore` file with sensible defaults
176
309
  3. Let you choose from starter workflow templates (hello-world, pr-checks)
177
310
  4. Install dependencies using the package manager detected for your repo (npm, pnpm, or yarn)
178
- 5. Update `.gitignore` to exclude `.kici/node_modules/`
311
+ 5. Update `.gitignore` to exclude `.kici/node_modules/`, and write `.kici/.gitignore` to keep the generated `types/` declarations untracked (`kici.lock.json` stays tracked — the orchestrator fetches it from your repo)
179
312
  6. Optionally install a pre-commit hook to auto-compile workflows
180
313
 
181
314
  The package manager is detected from your repo's `packageManager` field, lockfile, or the manager that invoked `kici`, defaulting to npm. Pass `--package-manager <npm|pnpm|yarn>` to override it.
@@ -496,7 +629,7 @@ For coding agents that want the entire documentation set up front, KiCI follows
496
629
 
497
630
  - `https://kici.dev/llms.txt` — curated link index grouped by SDK / patterns / CLI / architecture.
498
631
  - `https://kici.dev/llms-full.txt` — concatenated markdown of every page indexed above.
499
- - `kici docs llm` — print the same `llms-full.txt` bundle to stdout, offline, straight from the installed `@kici-dev/compiler` package. Add `--index` to print the curated `llms.txt` index instead. The agent can pipe the output into its own context buffer with no network call.
632
+ - `kici docs llm` — print the same curated `llms.txt` index to stdout, offline, straight from the installed `@kici-dev/compiler` package. `kici docs llm <topic>` prints one task bundle (`getting-started`, `patterns`, `sdk`, `sdk-runtime`, `cli`, `features`, `features-execution`, `providers`, `architecture`), and `kici docs llm full` prints the whole `llms-full.txt`. Add `--out <path>` to write to a file. The agent can pipe the output into its own context buffer with no network call.
500
633
  - `kici docs` — open the docs site in your browser.
501
634
 
502
635
  The offline bundle is regenerated from `docs/` every time the package is built, so it always matches the version of KiCI you've installed.
@@ -577,6 +577,181 @@ This workflow:
577
577
 
578
578
  ---
579
579
 
580
+ ## Git credentials
581
+
582
+ Source: https://docs.kici.dev/user/patterns/git-credentials/
583
+
584
+ Two facts drive everything on this page, and neither is guessable:
585
+
586
+ - **Cloning your own repository needs no credential.** The framework checks it
587
+ out for you, and the app KiCI installs already holds read access.
588
+ - **Pushing always needs a credential you supply.** The KiCI app holds read
589
+ access only, so every push — including to the job's own repository — needs one.
590
+
591
+ ## Declare credentials once, by name
592
+
593
+ Credentials are declared on the job as a named map. **Every value is the name of
594
+ a secret**, in `<context>:<secret-name>` form — never the credential itself:
595
+
596
+ ```typescript
597
+ job('release', {
598
+ runsOn: 'linux',
599
+ gitCredentials: {
600
+ // `default` is used whenever a call names no credential
601
+ default: {
602
+ kind: 'app',
603
+ appIdSecret: 'ci:ACME_APP_ID',
604
+ installationIdSecret: 'ci:ACME_INSTALL_ID',
605
+ privateKeySecret: 'ci:ACME_APP_KEY',
606
+ },
607
+ forge: { kind: 'token', tokenSecret: 'ci:FORGE_PAT' },
608
+ vendor: { kind: 'ssh', privateKeySecret: 'ci:VENDOR_DEPLOY_KEY' },
609
+ },
610
+ steps: [build, tagAndPush],
611
+ });
612
+ ```
613
+
614
+ Store the secrets first with `kici-admin secret set`. Pasting a private key
615
+ straight into the workflow is rejected when the workflow is defined, naming the
616
+ field — a key written into `.kici/` would be committed to your repository.
617
+
618
+ ## Push
619
+
620
+ Your checkout is read-only by default. Opening a write window is explicit:
621
+
622
+ ```typescript
623
+ step('tag', async ({ $, repo }) => {
624
+ await $`git tag v${version}`;
625
+ await repo.withWrite({ permissions: { contents: 'write' } }, async () => {
626
+ await $`git push origin v${version}`;
627
+ });
628
+ });
629
+ ```
630
+
631
+ Inside the callback, git operations on that repository use a write credential.
632
+ Outside it they do not, so an accidental push elsewhere in the job fails.
633
+
634
+ Pass `credential: 'forge'` to use a named entry instead of `default`.
635
+
636
+ **KiCI never guesses the permission set.** What a push needs depends on what is
637
+ being pushed — changing anything under `.github/workflows/` additionally requires
638
+ `workflows`:
639
+
640
+ ```typescript
641
+ await repo.withWrite({ permissions: { contents: 'write', workflows: 'write' } }, async () => {
642
+ await $`git push origin HEAD`;
643
+ });
644
+ ```
645
+
646
+ If the app was not granted a permission you request, the forge refuses to issue
647
+ the credential at all. The error names the repository and the permissions you
648
+ asked for, **before any git command runs** — not at the end of a long build.
649
+
650
+ ## Clone more than one repository
651
+
652
+ A workflow often needs several repositories, not just its own. Mint one token
653
+ that covers all of them, then clone each with it:
654
+
655
+ ```typescript
656
+ const { token } = await kici.git.github.getToken({
657
+ repositories: ['acme/app', 'acme/shared-lib'],
658
+ permissions: { contents: 'read' },
659
+ });
660
+
661
+ for (const repo of ['acme/app', 'acme/shared-lib']) {
662
+ await $`git clone https://x-access-token:${token}@github.com/${repo}.git`;
663
+ }
664
+ ```
665
+
666
+ A GitHub App token is issued per installation, so one call covers every
667
+ repository you name. Each repository must be inside the app's installation. If
668
+ one is not, the forge refuses the whole request and the error names it.
669
+
670
+ The credential helper is installed on your own checkout only, so a repository
671
+ you clone yourself does not inherit it. That is why this case mints a token
672
+ rather than relying on the helper.
673
+
674
+ ## Call the forge API
675
+
676
+ `gh` does not read git credential helpers, so this is the one case that wants the
677
+ token as a value:
678
+
679
+ ```typescript
680
+ const { token } = await kici.git.github.getToken({
681
+ repositories: ['acme/app'],
682
+ permissions: { contents: 'write' },
683
+ });
684
+ await $({ env: { ...process.env, GH_TOKEN: token } })`gh release create v${version}`;
685
+ ```
686
+
687
+ The token is masked in step logs. Prefer `withWrite` for git itself, which never
688
+ places a credential in the step environment.
689
+
690
+ ## Credentials that only exist at run time
691
+
692
+ A credential fetched during the run — from a vault, or a cloud secret store via
693
+ the job's OIDC identity — cannot be named ahead of time. Use the `*Value` half of
694
+ the pair, which says "this is the credential, not a name for one":
695
+
696
+ ```typescript
697
+ gitCredentials: { default: { kind: 'token', tokenValue: fetchedAtRuntime } }
698
+ ```
699
+
700
+ To pass a derived credential to a **later** job, publish it with
701
+ `ctx.setSecretOutput()` and name it with the reserved `needs:` context — that
702
+ path is encrypted, scoped to the run, and deleted when the run ends:
703
+
704
+ ```typescript
705
+ const mint = job('mint', {
706
+ runsOn: 'linux',
707
+ run: async (ctx) => {
708
+ const token = (await ctx.$`vault write -f auth/token/create`).stdout.trim();
709
+ ctx.setSecretOutput('FORGE_TOKEN', token);
710
+ },
711
+ });
712
+
713
+ const build = job('build', {
714
+ runsOn: 'linux',
715
+ needs: [mint],
716
+ gitCredentials: { default: { kind: 'token', tokenSecret: 'needs:FORGE_TOKEN' } },
717
+ steps: [cloneAndPush],
718
+ });
719
+ ```
720
+
721
+ Never put a credential in a regular job output: regular outputs are not masked,
722
+ are stored, and are shown in the dashboard.
723
+
724
+ For a **minted app token**, prefer re-deriving over transporting — those expire
725
+ after an hour, so one minted in an earlier job is often already dead by the time
726
+ a later job reads it. Have the later job name the same secret, or mint its own.
727
+
728
+ ## How it works, and why long jobs still push
729
+
730
+ An app token expires an hour after it is issued, and cannot be renewed. Rather
731
+ than capture one at checkout time, the agent installs a git credential helper on
732
+ the checkout: git asks it on every network operation, and it obtains a fresh
733
+ credential each time. A push at the end of a three-hour build works exactly as it
734
+ does at the start, and no credential is ever written into `.git/config`, into
735
+ `git remote -v`, or into the step's environment.
736
+
737
+ ## Limits worth knowing
738
+
739
+ - **Container jobs cannot use this yet.** A container job runs git inside the
740
+ container, which has no route to the credential service. Bare-metal jobs are
741
+ unaffected.
742
+ - **The reserved `needs:` context is not resolvable yet** on a deployed
743
+ orchestrator; naming it produces a clear error rather than a wrong credential.
744
+ - **A write window is bounded by the repository and the callback, not the step.**
745
+ Steps running concurrently in the same job can push to the same repository
746
+ while it is open. They cannot reach a different one.
747
+ - **A credential you supply yourself cannot be narrowed.** A personal access
748
+ token or SSH key grants whatever it was created with, so a requested permission
749
+ set is reported as unscoped rather than pretended to be enforced.
750
+ - **Being allowed to push is not the same as the push succeeding.** A branch
751
+ protection rule or repository ruleset can still reject it.
752
+
753
+ ---
754
+
580
755
  ## Host restart & wait-for-alive
581
756
 
582
757
  Source: https://docs.kici.dev/user/patterns/host-restart/
@@ -939,7 +1114,7 @@ export default workflow('on-forgejo-push', {
939
1114
  **Caveat — cloning:** generic webhook sources deliver only the payload; they do not carry a clone token, and KiCI's automatic pre-step clone (`packages/agent/src/checkout/git-clone.ts`) is GitHub-only today (HTTPS + `http.extraHeader` Basic auth with a GitHub installation token). Three practical patterns:
940
1115
 
941
1116
  - **Mirror to GitHub and fan out.** Keep the repo on GitHub, register the workflow via a GitHub default-branch push, and have Gogs/Forgejo webhooks fan out via [cross-source delivery](https://docs.kici.dev/architecture/webhooks/webhook-delivery/#cross-source-delivery). The clone runs against the GitHub mirror using the GitHub App's token.
942
- - **Clone yourself using a secret.** Set `checkout: false` on the job to skip the framework clone, store an SSH private key or forge personal access token as a secret, and run `git clone` explicitly in the first step. This works for any forge the agent can reach, no mirror needed. You still need a way to **register** the workflow — either keep a one-file GitHub repo whose only job is to own the registration, or bootstrap the registration manually against the orchestrator DB.
1117
+ - **Clone yourself using a declared credential.** Set `checkout: false` on the job to skip the framework clone, declare the forge credential on the job as a named `gitCredentials` entry (values are **secret names**, never the credential), and run `git clone` in the first step — the agent authenticates it for you, and the credential never appears in workflow source. See [Git credentials](https://docs.kici.dev/user/patterns/git-credentials/). This works for any forge the agent can reach, no mirror needed. You still need a way to **register** the workflow — either keep a one-file GitHub repo whose only job is to own the registration, or bootstrap the registration manually against the orchestrator DB.
943
1118
  - **Self-contained workflow.** No clone at all. The step reads whatever it needs from `rawPayload` (e.g., `rawPayload.after`, `rawPayload.repository.clone_url`) and drives external systems — notifications, deploys, third-party CI triggers.
944
1119
 
945
1120
  Manual-clone example (pattern 2) using an SSH deploy key:
@@ -144,38 +144,22 @@ the manifest flow — follow these steps.
144
144
 
145
145
  5. **Pick permissions.** Minimum required:
146
146
 
147
- | Scope | Access | Why |
148
- | --------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
149
- | Repository -> Contents | Read | Clone the repo to read the lock file |
150
- | Repository -> Metadata | Read (auto) | Default for every App; also lets KiCI look up a pull-request author's repository access level for CI trust |
151
- | Repository -> Pull requests | Read | Match `pull_request` triggers |
152
- | Repository -> Checks | Read & write | Post KiCI's enriched Check runs |
153
- | Organization -> Members | Read | (optional, org installs) Receive `organization` / `membership` / `team` events so KiCI's CI-trust permission cache invalidates promptly on access changes |
154
-
155
- The first four rows cover the core flow (clone, trigger matching,
156
- Check runs). The **Organization -> Members** row is only relevant if
157
- you use [CI trust tiers](https://docs.kici.dev/architecture/security/ci-security/)
158
- on an org-level install — see the event note below.
147
+ | Scope | Access | Why |
148
+ | --------------------------- | ------------ | ------------------------------------ |
149
+ | Repository -> Contents | Read | Clone the repo to read the lock file |
150
+ | Repository -> Metadata | Read (auto) | Default for every App |
151
+ | Repository -> Pull requests | Read | Match `pull_request` triggers |
152
+ | Repository -> Checks | Read & write | Post KiCI's enriched Check runs |
153
+
154
+ Those four cover the whole flow: clone, trigger matching, Check runs.
155
+ [CI trust](https://docs.kici.dev/architecture/security/ci-security/) needs no
156
+ permission of its own it reads the fork relationship straight out of
157
+ the webhook payload and calls no GitHub API.
159
158
 
160
159
  6. **Subscribe to events.** At minimum: `push`, `pull_request`,
161
160
  `check_run`, `check_suite`. Add others (`issues`, `release`, ...) if
162
161
  your workflows use those triggers.
163
162
 
164
- **For CI trust (optional but recommended on org installs):** also
165
- subscribe to `member`, `organization`, `membership`, and `team`.
166
- KiCI caches each pull-request author's repository access level (used
167
- to decide whether workflow changes take effect immediately or are
168
- held for approval — see
169
- [CI security](https://docs.kici.dev/architecture/security/ci-security/)). These
170
- events let the orchestrator drop stale cache entries the moment a
171
- contributor's access changes. They are not required for correctness:
172
- without them the cache simply ages out on its own 15-minute TTL, so a
173
- permission change can take up to 15 minutes to take effect. The
174
- `organization` / `membership` / `team` events require the
175
- **Organization -> Members** read permission and an org-level
176
- installation; `member` is a repository event covered by the default
177
- Metadata permission.
178
-
179
163
  7. **Generate a private key.** Scroll to the bottom of the App settings
180
164
  and click _Generate a private key_. A `.pem` file downloads —
181
165
  store it safely; you cannot redownload it.
@@ -109,7 +109,7 @@ An orchestrator enforces a few limits, each surfaced as a clear step error when
109
109
  - A **per-org storage quota** (20 GiB by default) across all non-expired artifacts.
110
110
  - An **expiry** (30 days by default) after which an artifact is no longer downloadable or listed.
111
111
 
112
- Operators can raise or lower the quota and expiry per organization; see the [orchestrator storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/#artifacts) for the operator-facing knobs.
112
+ Each of the four is a cluster-wide default an operator can raise or lower per organization; see the [orchestrator storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/#artifacts) for the operator-facing knobs.
113
113
 
114
114
  When an upload or download fails for a reason that is **not** one of these limits — the orchestrator has no artifact storage configured, or it could not service the request — the step error says exactly that, instead of reporting a quota rejection or a missing artifact. So an error that names a limit really is a limit you can act on, and an error that names an orchestrator problem is one to take to whoever runs it.
115
115
 
@@ -266,7 +266,7 @@ Source: https://docs.kici.dev/user/sdk/event-payloads/
266
266
 
267
267
  ## The envelope
268
268
 
269
- The normalized event envelope is the single event contract in KiCI. Rules receive it as `ctx.event`, and every dynamic function — `environment:`, `env:`, and `concurrencyGroup:` resolvers, generated jobs, and a workflow's `concurrency.group` — receives the same envelope as its argument.
269
+ The normalized event envelope is the single event contract in KiCI. Rules receive it as `ctx.event`, and every dynamic function — `context:`, `env:`, and `concurrencyGroup:` resolvers, generated jobs, and a workflow's `concurrency.group` — receives the same envelope as its argument.
270
270
 
271
271
  Narrow on the `type` field to branch per trigger kind (`if (event.type === 'push')`). The raw provider webhook body is nested at `payload`; the typed variants below describe its shape per event type.
272
272
 
@@ -712,7 +712,7 @@ export const setup = job('setup', {
712
712
 
713
713
  ## `checkStep(name, options)`
714
714
 
715
- The check-mode-aware sibling of `idempotentStep()`. It takes the **same option shape**, but behaves differently when a run is started in check mode (`kici run --check`):
715
+ The check-mode-aware sibling of `idempotentStep()`. It takes a closely related option shape, but behaves differently when a run is started in check mode (`kici run --check`):
716
716
 
717
717
  | Factory | Behavior under `kici run --check` |
718
718
  | ---------------- | ----------------------------------------- |
@@ -730,10 +730,16 @@ Use `checkStep()` for deploy-style steps where you want a dry-run preview of pen
730
730
  | `apply` | `(ctx, drift: TDrift) => Promise<TApplied>` | Yes | Brings the system to the desired state. Runs only in apply mode (skipped under `kici run --check`). |
731
731
  | `summarize` | `(drift: TDrift) => string` | Yes | Human-readable summary of what `apply()` would do; shown in check-mode drift output. |
732
732
  | `whenInSync` | `(ctx) => Promise<TInSync>` | No | Runs when `check()` returned `null` (already in sync). |
733
+ | `outputs` | `OutputSchema` | No | Zod schema validating the step's outputs at runtime. |
733
734
  | `continueOnError` | `boolean` | No | When true, the job proceeds even if this step fails. |
734
735
  | `timeout` | `number` | No | Step-level timeout in milliseconds. |
736
+ | `retry` | `number \| RetryConfig` | No | Retry policy for the step; `retry: N` is shorthand for `{ maxAttempts: N }`. |
737
+ | `cache` | `CacheInput` | No | Declarative cache restored before the step and saved after it succeeds. |
738
+ | `rules` | `Rule[]` | No | Step-level conditional rules, evaluated agent-side. |
735
739
 
736
- The one signature difference from `idempotentStep`: `apply` and `whenInSync` receive `ctx` as their first argument, so the apply logic has access to `ctx.$`, `ctx.log`, and `ctx.secrets`.
740
+ Everything from `outputs` down is a plain [`step()` option](https://docs.kici.dev/user/sdk/core/) forwarded to the underlying step. The three step options `checkStep` does **not** accept are `onCancel`, `cleanup`, and `approval`.
741
+
742
+ There are two signature differences from `idempotentStep`. First, `apply` and `whenInSync` receive `ctx` as their first argument, so the apply logic has access to `ctx.$`, `ctx.log`, and `ctx.secrets`. Second, `summarize` is **required** here, because it is what check mode prints; `idempotentStep` defaults it to a JSON dump of the drift. `idempotentStep` also takes none of the step-option passthroughs above.
737
743
 
738
744
  ### Result
739
745
 
@@ -841,6 +847,21 @@ const patch = job('patch', {
841
847
  });
842
848
  ```
843
849
 
850
+ ### Which hosts are targets
851
+
852
+ `runsOnAll` targets the hosts you declare as members of your fleet. A host becomes
853
+ a target when it registers with the orchestrator under a stable agent identity, or
854
+ when an operator declares it with `kici-admin host declare`.
855
+
856
+ Agents that an auto-scaler starts are **not** targets, even when their labels match
857
+ the predicate. An auto-scaler starts an agent at the fixed shape of its pool, so a
858
+ pinned child would run at that shape and not at its own. Auto-scaler agents stay
859
+ available to `runsOn` and to the queue, and `ctx.kici.inventory` continues to list
860
+ them.
861
+
862
+ The fleet preview and the host detail page in the web UI apply the same rule, so
863
+ they show the hosts a run targets.
864
+
844
865
  ### Input forms
845
866
 
846
867
  `runsOnAll` accepts three shapes:
@@ -961,7 +961,7 @@ Source: https://docs.kici.dev/user/sdk/rules-matrix-dynamic/
961
961
 
962
962
  Rules control conditional execution of workflows and jobs. A rule that returns `false` (or whose check function returns `false`) prevents execution.
963
963
 
964
- A rule's check function that **returns `false`** cleanly skips the job or step. A check function that **throws** is treated as an evaluation failure, not a skip: the job or step **fails** with the error surfaced (both on a remote run and when running locally with `kici run --local`), so a broken rule can never silently pass as a green run. For example, `rule('main only', (ctx) => ctx.event.ref.endsWith('main'))` throws on an event whose `ref` is undefined — that run fails with the error instead of quietly skipping every step. Fix the thrown error (guard the access) rather than relying on the skip.
964
+ A rule's check function that **returns `false`** cleanly skips the job or step. A check function that **throws** counts as an evaluation failure, not a skip. The job or step **fails** with the error surfaced (both on a remote run and when running locally with `kici run --local`), so a broken rule can never silently pass as a green run. For example, `rule('main only', (ctx) => ctx.event.ref.endsWith('main'))` throws on an event whose `ref` is undefined — that run fails with the error instead of quietly skipping every step. Fix the thrown error (guard the access) rather than relying on the skip.
965
965
 
966
966
  ### rule(label) / rule(label, check)
967
967
 
@@ -2435,6 +2435,36 @@ workflow('post-deploy', {
2435
2435
 
2436
2436
  The orchestrator automatically emits system events for workflow and job completions. You do not need to call `ctx.emit()` for these -- they are generated by the orchestrator after execution. Listen for them with `workflowComplete()` and `jobComplete()` triggers.
2437
2437
 
2438
+ ### Event scaler events
2439
+
2440
+ The [event scaler backend](https://docs.kici.dev/operator/orchestrator/event-scaler/) emits two reserved events that your provisioning and teardown workflows subscribe to. The SDK exports their names and payload schemas, so a workflow imports the contract instead of re-declaring it.
2441
+
2442
+ | Export | What it is |
2443
+ | ------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
2444
+ | `SCALER_EVENT_NAMES` | The two reserved event names: `SCALER_EVENT_NAMES.scaleUp` and `SCALER_EVENT_NAMES.scaleDown`. |
2445
+ | `ScalerScaleUpPayload` | Schema of the scale-up payload. `.parse(ctx.rawPayload)` returns the typed payload and rejects a malformed one. |
2446
+ | `ScalerScaleDownPayload` | Schema of the scale-down payload, including the narrowed `reason`. |
2447
+ | `ScaleDownReason` | Why the scaler asked for a teardown: `idle`, `job-complete`, `heartbeat-timeout`, `spawn-timeout`, `drain`, `shutdown`. |
2448
+
2449
+ ```ts
2450
+ import { workflow, job, kiciEvent, SCALER_EVENT_NAMES, ScalerScaleUpPayload } from '@kici-dev/sdk';
2451
+
2452
+ export default workflow('provision', {
2453
+ on: [kiciEvent({ name: SCALER_EVENT_NAMES.scaleUp, match: { '$.scalerName': 'hetzner' } })],
2454
+ jobs: [
2455
+ job('provision', {
2456
+ runsOn: ['kici:os:linux'],
2457
+ run: async (ctx) => {
2458
+ const payload = ScalerScaleUpPayload.parse(ctx.rawPayload);
2459
+ ctx.log.info(`provision agent ${payload.agentId}`);
2460
+ },
2461
+ }),
2462
+ ],
2463
+ });
2464
+ ```
2465
+
2466
+ These names are reserved. `ctx.emit()` rejects any event name that starts with `kici.`, so a workflow step cannot forge a scaler event. For every payload field, see the [event contract reference](https://docs.kici.dev/operator/orchestrator/event-scaler-events/). For complete provisioning and teardown workflows, see [autoscaling workflows](https://docs.kici.dev/user/workflows/autoscaling-workflows/).
2467
+
2438
2468
  ---
2439
2469
 
2440
2470
  ## SDK reference