@kici-dev/compiler 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -1
- package/dist/cli.js +27 -22
- package/dist/commands/cancel.js +1 -1
- package/dist/commands/compile.js +1 -1
- package/dist/commands/docs.js +1 -1
- package/dist/commands/drain-worker.js +1 -1
- package/dist/commands/endpoints.js +1 -1
- package/dist/commands/fixture.js +3 -3
- package/dist/commands/hook.js +1 -1
- package/dist/commands/index.js +6 -6
- package/dist/commands/init.js +9 -7
- package/dist/commands/login.js +5 -16
- package/dist/commands/run.js +19 -8
- package/dist/commands/secrets-list.js +2 -2
- package/dist/commands/status.js +4 -4
- package/dist/commands/test.d.ts +2 -0
- package/dist/commands/test.js +2 -2
- package/dist/commands/types.js +1 -1
- package/dist/commands/watch.js +1 -1
- package/dist/commands/workflows.js +3 -3
- package/dist/execution/executor.js +6 -3
- package/dist/execution/sdk-alias.js +1 -1
- package/dist/execution/ts-loader.d.ts +2 -0
- package/dist/execution/ts-loader.js +13 -0
- package/dist/fixtures/compiler.d.ts +7 -5
- package/dist/fixtures/compiler.js +10 -6
- package/dist/llm-context/llms-full.txt +1077 -387
- package/dist/llm-context/llms.txt +38 -37
- package/dist/local-executor/index.js +6 -5
- package/dist/local-executor/job-runner.js +6 -5
- package/dist/local-executor/materializer.d.ts +5 -0
- package/dist/local-executor/materializer.js +22 -1
- package/dist/local-executor/output-streamer.js +1 -1
- package/dist/local-executor/picker.js +1 -1
- package/dist/local-executor/to-event-payload.d.ts +16 -0
- package/dist/local-executor/to-event-payload.js +21 -0
- package/dist/local-executor/workflow-lock.d.ts +4 -3
- package/dist/local-executor/workflow-lock.js +0 -0
- package/dist/lockfile/generator.js +11 -6
- package/dist/lockfile/hasher.js +1 -1
- package/dist/remote/client.d.ts +4 -0
- package/dist/remote/client.js +1 -1
- package/dist/remote/encryption.d.ts +1 -1
- package/dist/remote/encryption.js +1 -1
- package/dist/remote/history.js +2 -2
- package/dist/remote/oauth.js +1 -1
- package/dist/remote/oidc-discovery.js +1 -1
- package/dist/remote/output/streaming.js +1 -1
- package/dist/remote/output/summary.js +1 -1
- package/dist/remote/prod-defaults.d.ts +18 -0
- package/dist/remote/prod-defaults.js +23 -0
- package/dist/remote/secret-upload.d.ts +20 -0
- package/dist/remote/secret-upload.js +58 -0
- package/dist/remote/uploader.js +1 -1
- package/dist/templates/index.js +1 -1
- package/dist/templates/package-json.js +1 -1
- package/dist/templates/workflows/pr-checks.js +3 -2
- package/dist/templates/workflows/pr-checks.ts +4 -2
- package/dist/test-runner/dry-run.js +1 -1
- package/dist/test-runner/index.js +4 -4
- package/dist/test-runner/job-executor.js +1 -1
- package/dist/test-runner/output-formatter.js +1 -1
- package/dist/test-runner/payload-builder.js +2 -2
- package/dist/test-runner/rule-evaluator.js +1 -1
- package/dist/test-runner/step-context.js +6 -2
- package/dist/types.d.ts +17 -1
- package/dist/types.js +1 -0
- package/dist/workflows/pr-checks.ts +4 -2
- package/package.json +13 -9
- package/sbom.spdx.json +1328 -8436
|
@@ -6,13 +6,13 @@ This file is the concatenated markdown of every KiCI documentation page intended
|
|
|
6
6
|
|
|
7
7
|
## User guide
|
|
8
8
|
|
|
9
|
-
Source: https://kici.dev/
|
|
9
|
+
Source: https://docs.kici.dev/user/
|
|
10
10
|
|
|
11
11
|
Documentation for workflow authors -- people writing CI/CD pipelines in TypeScript using the KiCI SDK and compiler. If you are defining workflows, running local tests, or learning the SDK API, start here.
|
|
12
12
|
|
|
13
13
|
## Pages
|
|
14
14
|
|
|
15
|
-
### [Getting started](getting-started.md)
|
|
15
|
+
### [Getting started with KiCI](getting-started.md)
|
|
16
16
|
|
|
17
17
|
Install the SDK and compiler, write your first workflow, compile it to a lock file, and test it locally with simulated events. Covers prerequisites (Node.js 24+, pnpm), the `kici init` command for scaffolding, and the relationship between workflows, the lock file, and the three-tier runtime.
|
|
18
18
|
|
|
@@ -88,7 +88,7 @@ Cross-repo workflows that let a single workflow repo define jobs which run on ev
|
|
|
88
88
|
|
|
89
89
|
## Getting started with KiCI
|
|
90
90
|
|
|
91
|
-
Source: https://kici.dev/
|
|
91
|
+
Source: https://docs.kici.dev/user/getting-started/
|
|
92
92
|
|
|
93
93
|
KiCI lets you define CI/CD workflows in TypeScript instead of YAML. You get full language power -- type safety, autocompletion, loops, conditionals, and async/await -- for your build pipelines.
|
|
94
94
|
|
|
@@ -179,7 +179,7 @@ Create `.kici/workflows/ci.ts`:
|
|
|
179
179
|
import { workflow, job, step, pr } from '@kici-dev/sdk';
|
|
180
180
|
|
|
181
181
|
const lint = job('lint', {
|
|
182
|
-
runsOn: '
|
|
182
|
+
runsOn: 'linux',
|
|
183
183
|
steps: [
|
|
184
184
|
step('install', async ({ $ }) => {
|
|
185
185
|
await $`pnpm install --frozen-lockfile`;
|
|
@@ -191,7 +191,7 @@ const lint = job('lint', {
|
|
|
191
191
|
});
|
|
192
192
|
|
|
193
193
|
const test = job('test', {
|
|
194
|
-
runsOn: '
|
|
194
|
+
runsOn: 'linux',
|
|
195
195
|
needs: [lint],
|
|
196
196
|
steps: [
|
|
197
197
|
step('install', async ({ $ }) => {
|
|
@@ -264,9 +264,9 @@ Workflow: ci
|
|
|
264
264
|
✓ Matched trigger 1
|
|
265
265
|
Jobs (2):
|
|
266
266
|
lint
|
|
267
|
-
runs-on:
|
|
267
|
+
runs-on: linux
|
|
268
268
|
test
|
|
269
|
-
runs-on:
|
|
269
|
+
runs-on: linux
|
|
270
270
|
|
|
271
271
|
Decision Summary:
|
|
272
272
|
|
|
@@ -310,7 +310,7 @@ This updates `.kici/package.json` and generates (or updates) `package-lock.json`
|
|
|
310
310
|
|
|
311
311
|
### Dependency resolution contract
|
|
312
312
|
|
|
313
|
-
Every `.kici/` dependency must be resolvable from the **single cloned repository**. When a job runs, the agent clones only this repository and installs `.kici/` dependencies with your repo's package manager (npm or pnpm
|
|
313
|
+
Every `.kici/` dependency must be resolvable from the **single cloned repository**. When a job runs, the agent clones only this repository and installs `.kici/` dependencies with your repo's package manager (npm or pnpm; yarn is not supported — the agent rejects it with an actionable error). A dependency that points outside the cloned repo cannot be resolved.
|
|
314
314
|
|
|
315
315
|
In practice:
|
|
316
316
|
|
|
@@ -480,7 +480,7 @@ The lock file approach means the orchestrator stays git-agnostic -- it only need
|
|
|
480
480
|
|
|
481
481
|
## 5-minute quickstart
|
|
482
482
|
|
|
483
|
-
Source: https://kici.dev/
|
|
483
|
+
Source: https://docs.kici.dev/user/quickstart/
|
|
484
484
|
|
|
485
485
|
KiCI offers two equally-supported quickstart paths. Pick the one that fits your machine — both end with the same working pipeline (orchestrator + agent + your first workflow run visible in the dashboard).
|
|
486
486
|
|
|
@@ -517,7 +517,7 @@ Both quickstarts deploy a real orchestrator + agent. If you only want to write a
|
|
|
517
517
|
|
|
518
518
|
## Basic workflow patterns
|
|
519
519
|
|
|
520
|
-
Source: https://kici.dev/
|
|
520
|
+
Source: https://docs.kici.dev/user/patterns/basic/
|
|
521
521
|
|
|
522
522
|
A standard lint-then-test pipeline using job dependencies (`needs`):
|
|
523
523
|
|
|
@@ -525,7 +525,7 @@ A standard lint-then-test pipeline using job dependencies (`needs`):
|
|
|
525
525
|
import { workflow, job, step, pr } from '@kici-dev/sdk';
|
|
526
526
|
|
|
527
527
|
const lint = job('lint', {
|
|
528
|
-
runsOn: '
|
|
528
|
+
runsOn: 'linux',
|
|
529
529
|
steps: [
|
|
530
530
|
step('install', async ({ $ }) => {
|
|
531
531
|
await $`pnpm install --frozen-lockfile`;
|
|
@@ -538,7 +538,7 @@ const lint = job('lint', {
|
|
|
538
538
|
});
|
|
539
539
|
|
|
540
540
|
const test = job('test', {
|
|
541
|
-
runsOn: '
|
|
541
|
+
runsOn: 'linux',
|
|
542
542
|
needs: [lint],
|
|
543
543
|
steps: [
|
|
544
544
|
step('install', async ({ $ }) => {
|
|
@@ -551,7 +551,7 @@ const test = job('test', {
|
|
|
551
551
|
});
|
|
552
552
|
|
|
553
553
|
const typecheck = job('typecheck', {
|
|
554
|
-
runsOn: '
|
|
554
|
+
runsOn: 'linux',
|
|
555
555
|
needs: [lint],
|
|
556
556
|
steps: [
|
|
557
557
|
step('install', async ({ $ }) => {
|
|
@@ -608,7 +608,7 @@ const trigger = pr({
|
|
|
608
608
|
});
|
|
609
609
|
|
|
610
610
|
const build = job('build', {
|
|
611
|
-
runsOn: '
|
|
611
|
+
runsOn: 'linux',
|
|
612
612
|
steps: [
|
|
613
613
|
step('build', async ({ $ }) => {
|
|
614
614
|
await $`pnpm build`;
|
|
@@ -643,7 +643,7 @@ import { workflow, job, step, push } from '@kici-dev/sdk';
|
|
|
643
643
|
|
|
644
644
|
// Deploy on pushes to main
|
|
645
645
|
const deploy = job('deploy', {
|
|
646
|
-
runsOn: '
|
|
646
|
+
runsOn: 'linux',
|
|
647
647
|
steps: [
|
|
648
648
|
step('deploy', async ({ $ }) => {
|
|
649
649
|
await $`pnpm build`;
|
|
@@ -687,7 +687,7 @@ A workflow can respond to multiple trigger types:
|
|
|
687
687
|
import { workflow, job, step, pr, push } from '@kici-dev/sdk';
|
|
688
688
|
|
|
689
689
|
const test = job('test', {
|
|
690
|
-
runsOn: '
|
|
690
|
+
runsOn: 'linux',
|
|
691
691
|
steps: [
|
|
692
692
|
step('test', async ({ $ }) => {
|
|
693
693
|
await $`pnpm test`;
|
|
@@ -719,7 +719,7 @@ export default workflow('hello-world', {
|
|
|
719
719
|
on: dispatch(),
|
|
720
720
|
jobs: [
|
|
721
721
|
job('greet', {
|
|
722
|
-
runsOn: '
|
|
722
|
+
runsOn: 'linux',
|
|
723
723
|
steps: [
|
|
724
724
|
step('say-hello', async ({ $ }) => {
|
|
725
725
|
await $`echo "Hello, World!"`;
|
|
@@ -767,7 +767,7 @@ Leave `dispatch()` unfiltered while you drive it from `kici run local`. The CLI
|
|
|
767
767
|
|
|
768
768
|
## Conditionals & matrix patterns
|
|
769
769
|
|
|
770
|
-
Source: https://kici.dev/
|
|
770
|
+
Source: https://docs.kici.dev/user/patterns/conditionals-matrix/
|
|
771
771
|
|
|
772
772
|
Rules control whether a workflow or job runs. Use `rule()` for conditions that must pass, and `skip()` for conditions that should skip execution.
|
|
773
773
|
|
|
@@ -777,7 +777,7 @@ Rules control whether a workflow or job runs. Use `rule()` for conditions that m
|
|
|
777
777
|
import { workflow, job, step, pr, rule } from '@kici-dev/sdk';
|
|
778
778
|
|
|
779
779
|
const test = job('test', {
|
|
780
|
-
runsOn: '
|
|
780
|
+
runsOn: 'linux',
|
|
781
781
|
steps: [
|
|
782
782
|
step('test', async ({ $ }) => {
|
|
783
783
|
await $`pnpm test`;
|
|
@@ -802,7 +802,7 @@ export default workflow('ci', {
|
|
|
802
802
|
import { workflow, job, step, pr, rule, skip } from '@kici-dev/sdk';
|
|
803
803
|
|
|
804
804
|
const unitTests = job('unit-tests', {
|
|
805
|
-
runsOn: '
|
|
805
|
+
runsOn: 'linux',
|
|
806
806
|
steps: [
|
|
807
807
|
step('test', async ({ $ }) => {
|
|
808
808
|
await $`pnpm test:unit`;
|
|
@@ -811,7 +811,7 @@ const unitTests = job('unit-tests', {
|
|
|
811
811
|
});
|
|
812
812
|
|
|
813
813
|
const e2eTests = job('e2e-tests', {
|
|
814
|
-
runsOn: '
|
|
814
|
+
runsOn: 'linux',
|
|
815
815
|
rules: [
|
|
816
816
|
// Skip E2E when only docs change
|
|
817
817
|
skip('docs only', async (ctx) => {
|
|
@@ -862,7 +862,7 @@ Run a job for each value in an array:
|
|
|
862
862
|
import { workflow, job, step, push } from '@kici-dev/sdk';
|
|
863
863
|
|
|
864
864
|
const test = job('test', {
|
|
865
|
-
runsOn: '
|
|
865
|
+
runsOn: 'linux',
|
|
866
866
|
matrix: ['18', '20', '22'],
|
|
867
867
|
steps: [
|
|
868
868
|
step('test', async ({ $, matrix }) => {
|
|
@@ -886,14 +886,14 @@ Use an object to define multiple dimensions. KiCI expands all combinations (capp
|
|
|
886
886
|
|
|
887
887
|
```typescript
|
|
888
888
|
const test = job('test', {
|
|
889
|
-
runsOn: '
|
|
889
|
+
runsOn: ['linux', 'kici:agent:container'],
|
|
890
890
|
matrix: {
|
|
891
|
-
os: ['
|
|
891
|
+
os: ['linux', 'arm64'],
|
|
892
892
|
node: ['18', '20', '22'],
|
|
893
893
|
},
|
|
894
894
|
steps: [
|
|
895
895
|
step('test', async ({ $, matrix }) => {
|
|
896
|
-
// matrix.os = '
|
|
896
|
+
// matrix.os = 'linux' | 'arm64'
|
|
897
897
|
// matrix.node = '18' | '20' | '22'
|
|
898
898
|
await $`echo "Testing on ${matrix!.os} with Node ${matrix!.node}"`;
|
|
899
899
|
await $`pnpm test`;
|
|
@@ -904,21 +904,27 @@ const test = job('test', {
|
|
|
904
904
|
|
|
905
905
|
This creates 6 job instances (2 OS x 3 Node versions).
|
|
906
906
|
|
|
907
|
+
> **Labels are customer-defined.** `runsOn` values such as `linux` or `arm64` are scaler
|
|
908
|
+
> labels **you** define in your orchestrator's `labelSets` — they are matched by subset
|
|
909
|
+
> semantics, not by a hosted-runner name. You can also target reserved auto-injected labels
|
|
910
|
+
> in the `kici:` namespace (e.g. `kici:agent:firecracker`, `kici:agent:container`) to pin a
|
|
911
|
+
> job to a specific backend type.
|
|
912
|
+
|
|
907
913
|
### Include and exclude
|
|
908
914
|
|
|
909
915
|
Fine-tune matrix combinations:
|
|
910
916
|
|
|
911
917
|
```typescript
|
|
912
918
|
const test = job('test', {
|
|
913
|
-
runsOn: '
|
|
919
|
+
runsOn: 'linux',
|
|
914
920
|
matrix: {
|
|
915
|
-
os: ['
|
|
921
|
+
os: ['linux', 'arm64', 'windows'],
|
|
916
922
|
node: ['18', '20', '22'],
|
|
917
923
|
},
|
|
918
924
|
// Remove specific combination
|
|
919
|
-
exclude: [{ os: 'windows
|
|
925
|
+
exclude: [{ os: 'windows', node: '18' }],
|
|
920
926
|
// Add specific combination not in the matrix
|
|
921
|
-
include: [{ os: '
|
|
927
|
+
include: [{ os: 'linux', node: '23' }],
|
|
922
928
|
steps: [
|
|
923
929
|
step('test', async ({ $ }) => {
|
|
924
930
|
await $`pnpm test`;
|
|
@@ -935,7 +941,7 @@ Compute matrix values at runtime using an async function:
|
|
|
935
941
|
|
|
936
942
|
```typescript
|
|
937
943
|
const test = job('test', {
|
|
938
|
-
runsOn: '
|
|
944
|
+
runsOn: 'linux',
|
|
939
945
|
matrix: async ({ $ }) => {
|
|
940
946
|
// Discover packages in a monorepo
|
|
941
947
|
const result = await $`ls packages/`;
|
|
@@ -984,7 +990,7 @@ const discoverAndTest: DynamicJobFn = async ({ $ }) => {
|
|
|
984
990
|
|
|
985
991
|
return packages.map((pkg) =>
|
|
986
992
|
job(`test-${pkg}`, {
|
|
987
|
-
runsOn: '
|
|
993
|
+
runsOn: 'linux',
|
|
988
994
|
steps: [
|
|
989
995
|
step('test', async ({ $ }) => {
|
|
990
996
|
await $`cd packages/${pkg} && pnpm test`;
|
|
@@ -1006,7 +1012,7 @@ The `jobs` array accepts both static `Job` objects and `DynamicJobFn` functions:
|
|
|
1006
1012
|
|
|
1007
1013
|
```typescript
|
|
1008
1014
|
const lint = job('lint', {
|
|
1009
|
-
runsOn: '
|
|
1015
|
+
runsOn: 'linux',
|
|
1010
1016
|
steps: [
|
|
1011
1017
|
step('lint', async ({ $ }) => {
|
|
1012
1018
|
await $`pnpm lint`;
|
|
@@ -1036,7 +1042,7 @@ const prTrigger = pr({ target: 'main', paths: ['src/**', 'packages/**', '!**/*.m
|
|
|
1036
1042
|
const pushTrigger = push({ branches: 'main' });
|
|
1037
1043
|
|
|
1038
1044
|
const lint = job('lint', {
|
|
1039
|
-
runsOn: '
|
|
1045
|
+
runsOn: 'linux',
|
|
1040
1046
|
steps: [
|
|
1041
1047
|
step('install', async ({ $ }) => {
|
|
1042
1048
|
await $`pnpm install --frozen-lockfile`;
|
|
@@ -1048,7 +1054,7 @@ const lint = job('lint', {
|
|
|
1048
1054
|
});
|
|
1049
1055
|
|
|
1050
1056
|
const test = job('test', {
|
|
1051
|
-
runsOn: '
|
|
1057
|
+
runsOn: 'linux',
|
|
1052
1058
|
needs: [lint],
|
|
1053
1059
|
matrix: { node: ['18', '20', '22'] },
|
|
1054
1060
|
steps: [
|
|
@@ -1059,7 +1065,7 @@ const test = job('test', {
|
|
|
1059
1065
|
});
|
|
1060
1066
|
|
|
1061
1067
|
const deploy = job('deploy', {
|
|
1062
|
-
runsOn: '
|
|
1068
|
+
runsOn: 'linux',
|
|
1063
1069
|
needs: [test],
|
|
1064
1070
|
rules: [
|
|
1065
1071
|
// Only deploy from push events (not PRs)
|
|
@@ -1098,7 +1104,7 @@ This workflow:
|
|
|
1098
1104
|
|
|
1099
1105
|
## Integration patterns
|
|
1100
1106
|
|
|
1101
|
-
Source: https://kici.dev/
|
|
1107
|
+
Source: https://docs.kici.dev/user/patterns/integrations/
|
|
1102
1108
|
|
|
1103
1109
|
Use internal event triggers to chain workflows together. Workflow A completes, emits an event (or the system auto-emits a completion event), and Workflow B triggers in response.
|
|
1104
1110
|
|
|
@@ -1114,7 +1120,7 @@ export const deploy = workflow('deploy', {
|
|
|
1114
1120
|
on: push({ branches: 'main' }),
|
|
1115
1121
|
jobs: [
|
|
1116
1122
|
job('deploy', {
|
|
1117
|
-
runsOn: '
|
|
1123
|
+
runsOn: 'linux',
|
|
1118
1124
|
steps: [
|
|
1119
1125
|
step('deploy', async ({ $ }) => {
|
|
1120
1126
|
await $`./scripts/deploy.sh`;
|
|
@@ -1129,7 +1135,7 @@ export const postDeploy = workflow('post-deploy', {
|
|
|
1129
1135
|
on: workflowComplete({ name: 'deploy', status: ['success'] }),
|
|
1130
1136
|
jobs: [
|
|
1131
1137
|
job('notify', {
|
|
1132
|
-
runsOn: '
|
|
1138
|
+
runsOn: 'linux',
|
|
1133
1139
|
steps: [
|
|
1134
1140
|
step('slack', async ({ $ }) => {
|
|
1135
1141
|
await $`./scripts/notify-slack.sh "Deploy succeeded"`;
|
|
@@ -1152,7 +1158,7 @@ export const deploy = workflow('deploy', {
|
|
|
1152
1158
|
on: push({ branches: 'main' }),
|
|
1153
1159
|
jobs: [
|
|
1154
1160
|
job('deploy', {
|
|
1155
|
-
runsOn: '
|
|
1161
|
+
runsOn: 'linux',
|
|
1156
1162
|
steps: [
|
|
1157
1163
|
step('deploy', async ({ $ }) => {
|
|
1158
1164
|
await $`./scripts/deploy.sh`;
|
|
@@ -1173,7 +1179,7 @@ export const postDeploy = workflow('post-deploy', {
|
|
|
1173
1179
|
on: kiciEvent({ name: 'deploy-complete', match: { '$.env': 'prod' } }),
|
|
1174
1180
|
jobs: [
|
|
1175
1181
|
job('smoke-test', {
|
|
1176
|
-
runsOn: '
|
|
1182
|
+
runsOn: 'linux',
|
|
1177
1183
|
steps: [
|
|
1178
1184
|
step('test', async ({ $ }) => {
|
|
1179
1185
|
await $`./scripts/smoke-test.sh`;
|
|
@@ -1198,7 +1204,7 @@ export default workflow('on-argocd-deploy', {
|
|
|
1198
1204
|
on: genericWebhook({ source: 'argocd', events: ['deploy.success'] }),
|
|
1199
1205
|
jobs: [
|
|
1200
1206
|
job('post-deploy', {
|
|
1201
|
-
runsOn: '
|
|
1207
|
+
runsOn: 'linux',
|
|
1202
1208
|
steps: [
|
|
1203
1209
|
step('verify', async ({ $, rawPayload }) => {
|
|
1204
1210
|
// rawPayload contains the full webhook body from ArgoCD
|
|
@@ -1232,7 +1238,7 @@ export default workflow('stripe-invoice-handler', {
|
|
|
1232
1238
|
}),
|
|
1233
1239
|
jobs: [
|
|
1234
1240
|
job('process-invoice', {
|
|
1235
|
-
runsOn: '
|
|
1241
|
+
runsOn: 'linux',
|
|
1236
1242
|
steps: [
|
|
1237
1243
|
step('extract-customer', async ({ $, log }) => {
|
|
1238
1244
|
log.info('Processing paid invoice from Stripe');
|
|
@@ -1289,7 +1295,7 @@ export default workflow('on-forgejo-push', {
|
|
|
1289
1295
|
}),
|
|
1290
1296
|
jobs: [
|
|
1291
1297
|
job('react-to-push', {
|
|
1292
|
-
runsOn: '
|
|
1298
|
+
runsOn: 'linux',
|
|
1293
1299
|
steps: [
|
|
1294
1300
|
step('log', async ({ rawPayload, log }) => {
|
|
1295
1301
|
const ref = (rawPayload as { ref?: string }).ref;
|
|
@@ -1311,7 +1317,7 @@ Manual-clone example (pattern 2) using an SSH deploy key:
|
|
|
1311
1317
|
|
|
1312
1318
|
```typescript
|
|
1313
1319
|
job('forgejo-ci', {
|
|
1314
|
-
runsOn: '
|
|
1320
|
+
runsOn: 'linux',
|
|
1315
1321
|
checkout: false, // skip framework clone
|
|
1316
1322
|
steps: [
|
|
1317
1323
|
step('clone', async ({ $, ctx, rawPayload }) => {
|
|
@@ -1356,7 +1362,7 @@ kici-admin source add generic \
|
|
|
1356
1362
|
--rate-limit 120
|
|
1357
1363
|
|
|
1358
1364
|
# Patch the verificationConfig to use GitHub's signature header
|
|
1359
|
-
# (the CLI
|
|
1365
|
+
# (the CLI has no --signature-header flag; use the admin REST API):
|
|
1360
1366
|
curl -X PATCH https://<orchestrator>/api/v1/admin/generic-sources/<sourceId> \
|
|
1361
1367
|
-H "Authorization: Bearer <admin-token>" \
|
|
1362
1368
|
-H "Content-Type: application/json" \
|
|
@@ -1383,7 +1389,7 @@ export default workflow('on-github-repo-push', {
|
|
|
1383
1389
|
}),
|
|
1384
1390
|
jobs: [
|
|
1385
1391
|
job('notify', {
|
|
1386
|
-
runsOn: '
|
|
1392
|
+
runsOn: 'linux',
|
|
1387
1393
|
checkout: false, // no App token -> skip auto-clone
|
|
1388
1394
|
steps: [
|
|
1389
1395
|
step('log', async ({ rawPayload, log }) => {
|
|
@@ -1412,7 +1418,7 @@ export default workflow('on-github-repo-push', {
|
|
|
1412
1418
|
|
|
1413
1419
|
## Pattern reference
|
|
1414
1420
|
|
|
1415
|
-
Source: https://kici.dev/
|
|
1421
|
+
Source: https://docs.kici.dev/user/patterns/reference/
|
|
1416
1422
|
|
|
1417
1423
|
Every step receives a `StepContext` with these properties:
|
|
1418
1424
|
|
|
@@ -1490,7 +1496,7 @@ pnpm kici compile # Regenerates kici.lock.json with source locations
|
|
|
1490
1496
|
|
|
1491
1497
|
## Scheduling & event patterns
|
|
1492
1498
|
|
|
1493
|
-
Source: https://kici.dev/
|
|
1499
|
+
Source: https://docs.kici.dev/user/patterns/scheduling-and-events/
|
|
1494
1500
|
|
|
1495
1501
|
Run a full build and test suite on a schedule using `schedule()`. Schedule triggers are evaluated by the orchestrator's Raft leader in clustered deployments.
|
|
1496
1502
|
|
|
@@ -1502,7 +1508,7 @@ const install = step('install', async ({ $ }) => {
|
|
|
1502
1508
|
});
|
|
1503
1509
|
|
|
1504
1510
|
const fullTest = job('full-test', {
|
|
1505
|
-
runsOn: '
|
|
1511
|
+
runsOn: 'linux',
|
|
1506
1512
|
steps: [
|
|
1507
1513
|
install,
|
|
1508
1514
|
step('test', async ({ $ }) => {
|
|
@@ -1515,7 +1521,7 @@ const fullTest = job('full-test', {
|
|
|
1515
1521
|
});
|
|
1516
1522
|
|
|
1517
1523
|
const publish = job('publish-nightly', {
|
|
1518
|
-
runsOn: '
|
|
1524
|
+
runsOn: 'linux',
|
|
1519
1525
|
needs: [fullTest],
|
|
1520
1526
|
steps: [
|
|
1521
1527
|
install,
|
|
@@ -1559,7 +1565,7 @@ export const build = workflow('build', {
|
|
|
1559
1565
|
on: push({ branches: 'main' }),
|
|
1560
1566
|
jobs: [
|
|
1561
1567
|
job('test', {
|
|
1562
|
-
runsOn: '
|
|
1568
|
+
runsOn: 'linux',
|
|
1563
1569
|
steps: [
|
|
1564
1570
|
step('install', async ({ $ }) => {
|
|
1565
1571
|
await $`pnpm install --frozen-lockfile`;
|
|
@@ -1580,7 +1586,7 @@ export const deploy = workflow('deploy-on-success', {
|
|
|
1580
1586
|
on: workflowComplete({ name: 'build', status: ['success'] }),
|
|
1581
1587
|
jobs: [
|
|
1582
1588
|
job('deploy', {
|
|
1583
|
-
runsOn: '
|
|
1589
|
+
runsOn: 'linux',
|
|
1584
1590
|
steps: [
|
|
1585
1591
|
step('deploy-staging', async ({ $ }) => {
|
|
1586
1592
|
await $`./scripts/deploy.sh staging`;
|
|
@@ -1627,7 +1633,7 @@ export const testSuite = workflow('test-suite', {
|
|
|
1627
1633
|
on: push({ branches: 'main' }),
|
|
1628
1634
|
jobs: [
|
|
1629
1635
|
job('test', {
|
|
1630
|
-
runsOn: '
|
|
1636
|
+
runsOn: 'linux',
|
|
1631
1637
|
steps: [
|
|
1632
1638
|
step('install', async ({ $ }) => {
|
|
1633
1639
|
await $`pnpm install --frozen-lockfile`;
|
|
@@ -1653,7 +1659,7 @@ export const autoDeploy = workflow('auto-deploy', {
|
|
|
1653
1659
|
on: kiciEvent({ name: 'tests-passed' }),
|
|
1654
1660
|
jobs: [
|
|
1655
1661
|
job('deploy', {
|
|
1656
|
-
runsOn: '
|
|
1662
|
+
runsOn: 'linux',
|
|
1657
1663
|
steps: [
|
|
1658
1664
|
step('deploy', async ({ $ }) => {
|
|
1659
1665
|
await $`./scripts/deploy.sh`;
|
|
@@ -1682,9 +1688,152 @@ export const autoDeploy = workflow('auto-deploy', {
|
|
|
1682
1688
|
|
|
1683
1689
|
# SDK reference
|
|
1684
1690
|
|
|
1691
|
+
## Caching
|
|
1692
|
+
|
|
1693
|
+
Source: https://docs.kici.dev/user/sdk/caching/
|
|
1694
|
+
|
|
1695
|
+
KiCI ships a general-purpose cache for any files or directories your workflow produces — compiled artifacts, downloaded toolchains, package manager stores, build outputs. A cache entry is keyed, immutable once written, and shared across runs of the same repository so a later run can restore what an earlier run produced instead of recomputing it.
|
|
1696
|
+
|
|
1697
|
+
Two surfaces drive the same cache:
|
|
1698
|
+
|
|
1699
|
+
- **Declarative** — a `cache` field on a job or a step. The runtime restores before the work runs and saves after it succeeds, with no code in your step body.
|
|
1700
|
+
- **Imperative** — `ctx.cache.restore(spec)` / `ctx.cache.save(spec)` inside a step body, for fine-grained control over when restore and save happen.
|
|
1701
|
+
|
|
1702
|
+
The cache is backed by the orchestrator's object storage. Entries are isolated per organization and per ref scope (see [Isolation](#isolation)); no other tenant can read your cache, and an untrusted/fork ref can never poison the cache a trusted branch reads.
|
|
1703
|
+
|
|
1704
|
+
## CacheSpec
|
|
1705
|
+
|
|
1706
|
+
Both surfaces take the same shape:
|
|
1707
|
+
|
|
1708
|
+
```typescript
|
|
1709
|
+
interface CacheSpec {
|
|
1710
|
+
/** Exact cache key. First save wins; re-saving an existing key is a no-op. */
|
|
1711
|
+
key: string;
|
|
1712
|
+
/** Files/directories to cache. Repo-root-relative or `~`-prefixed. */
|
|
1713
|
+
paths: string[];
|
|
1714
|
+
/** Ordered prefix fallbacks for partial restore; newest matching entry wins. */
|
|
1715
|
+
restoreKeys?: string[];
|
|
1716
|
+
}
|
|
1717
|
+
```
|
|
1718
|
+
|
|
1719
|
+
- **`key`** is the exact cache key. It is **immutable** — the first save under a given key wins, and any later save under the same exact key is a no-op (the existing entry is never overwritten). Build keys from inputs that change when the cached content should change, e.g. a hash of your lockfile: `` key: `deps-${await ctx.$`sha256sum pnpm-lock.yaml`}` ``.
|
|
1720
|
+
- **`paths`** are the files and directories to archive, repo-root-relative or `~`-prefixed (the agent expands `~` to the workspace home). At least one path is required.
|
|
1721
|
+
- **`restoreKeys`** are ordered **prefix** fallbacks tried only when the exact `key` misses on restore. Each prefix is matched against existing entries; the **newest** matching entry wins. This lets a run that changed its lockfile still restore the closest previous cache and rebuild incrementally.
|
|
1722
|
+
|
|
1723
|
+
## Declarative cache
|
|
1724
|
+
|
|
1725
|
+
Add a `cache` field to a job or a step. It accepts one `CacheSpec` or an array of them. The runtime restores every spec before the job/step runs (surfaced as a `cache:restore` pseudo-step) and saves every spec after it completes successfully (surfaced as a `cache:save` pseudo-step):
|
|
1726
|
+
|
|
1727
|
+
```typescript
|
|
1728
|
+
import { job } from '@kici-dev/sdk';
|
|
1729
|
+
|
|
1730
|
+
job('build', {
|
|
1731
|
+
runsOn: 'linux-x64',
|
|
1732
|
+
cache: {
|
|
1733
|
+
key: 'mise-tools-v1',
|
|
1734
|
+
paths: ['~/.local/share/mise'],
|
|
1735
|
+
},
|
|
1736
|
+
steps: [
|
|
1737
|
+
step('install-tools', async (ctx) => {
|
|
1738
|
+
await ctx.$`mise install`;
|
|
1739
|
+
}),
|
|
1740
|
+
step('build', async (ctx) => {
|
|
1741
|
+
await ctx.$`mise exec -- pnpm build`;
|
|
1742
|
+
}),
|
|
1743
|
+
],
|
|
1744
|
+
});
|
|
1745
|
+
```
|
|
1746
|
+
|
|
1747
|
+
Step-level cache scopes the restore/save to a single step:
|
|
1748
|
+
|
|
1749
|
+
```typescript
|
|
1750
|
+
step('deps', {
|
|
1751
|
+
cache: { key: `npm-${lockfileHash}`, paths: ['node_modules'], restoreKeys: ['npm-'] },
|
|
1752
|
+
run: async (ctx) => {
|
|
1753
|
+
await ctx.$`pnpm install --frozen-lockfile`;
|
|
1754
|
+
},
|
|
1755
|
+
});
|
|
1756
|
+
```
|
|
1757
|
+
|
|
1758
|
+
On a cache **hit**, the archived paths are restored before the step body runs, so `pnpm install` sees a warm `node_modules`. On a **miss**, the step runs cold and the resulting paths are saved under the exact key for the next run.
|
|
1759
|
+
|
|
1760
|
+
## Imperative cache (`ctx.cache`)
|
|
1761
|
+
|
|
1762
|
+
When you need to decide at runtime whether to restore or save — for example, save only when a build actually changed something — use the imperative API on the step context:
|
|
1763
|
+
|
|
1764
|
+
```typescript
|
|
1765
|
+
step('build', async (ctx) => {
|
|
1766
|
+
const result = await ctx.cache.restore({
|
|
1767
|
+
key: `build-${sourceHash}`,
|
|
1768
|
+
paths: ['dist'],
|
|
1769
|
+
restoreKeys: ['build-'],
|
|
1770
|
+
});
|
|
1771
|
+
|
|
1772
|
+
if (result.hit) {
|
|
1773
|
+
ctx.log.info(`restored cache (matched ${result.matchedKey})`);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
await ctx.$`pnpm build`;
|
|
1777
|
+
|
|
1778
|
+
await ctx.cache.save({ key: `build-${sourceHash}`, paths: ['dist'] });
|
|
1779
|
+
});
|
|
1780
|
+
```
|
|
1781
|
+
|
|
1782
|
+
`restore(spec)` returns `{ hit, matchedKey? }`:
|
|
1783
|
+
|
|
1784
|
+
- `hit` is `true` when the exact `key` matched **or** a `restoreKeys` prefix matched.
|
|
1785
|
+
- `matchedKey` is the full key that actually matched — the exact key on a direct hit, or the full key of the matched prefix entry on a fallback hit.
|
|
1786
|
+
|
|
1787
|
+
`save(spec)` archives `spec.paths` under `spec.key`. Like the declarative surface, it is immutable: the first save under an exact key wins, and re-saving the same key is a no-op.
|
|
1788
|
+
|
|
1789
|
+
## Restore semantics
|
|
1790
|
+
|
|
1791
|
+
A restore resolves in this order:
|
|
1792
|
+
|
|
1793
|
+
1. **Exact key.** If an entry exists under the exact `key`, it is restored and `matchedKey === key`.
|
|
1794
|
+
2. **restoreKeys prefix fallback.** Each `restoreKeys` prefix is tried in order. Within a prefix, the **newest** matching entry wins; `matchedKey` is that entry's full key.
|
|
1795
|
+
3. **Miss.** If nothing matches, `hit` is `false` and no paths are restored.
|
|
1796
|
+
|
|
1797
|
+
This mirrors the familiar lockfile-hash pattern: key the entry on the exact lockfile hash, and add a `restoreKeys` prefix so a changed lockfile still restores the most recent prior cache to rebuild from.
|
|
1798
|
+
|
|
1799
|
+
## Immutability
|
|
1800
|
+
|
|
1801
|
+
Cache keys are write-once. The **first** save under an exact key wins; every subsequent save under that same exact key is a no-op and the original bytes are preserved. To publish new content, use a new key (typically by including a content hash in the key). Immutability is what makes a cache hit safe to trust — the bytes behind a given key never change after they are first written.
|
|
1802
|
+
|
|
1803
|
+
## Isolation
|
|
1804
|
+
|
|
1805
|
+
Each cache entry is scoped to your organization and to the ref's trust level:
|
|
1806
|
+
|
|
1807
|
+
- **Trusted refs** (your repository's own branches, default branch) read and write a **shared** scope visible to the whole org for that repository.
|
|
1808
|
+
- **Untrusted / fork refs** read the shared scope as a fallback but write to an **isolated** per-run scope. A fork build can therefore benefit from a warm cache the trusted branch produced, but can never write into the shared scope — so a malicious fork cannot poison the cache a trusted branch later restores.
|
|
1809
|
+
|
|
1810
|
+
No tenant can read another tenant's cache; the org boundary is enforced in the cache key namespace.
|
|
1811
|
+
|
|
1812
|
+
## Eviction
|
|
1813
|
+
|
|
1814
|
+
Cache storage is bounded per organization. Two mechanisms keep it bounded:
|
|
1815
|
+
|
|
1816
|
+
- **Quota** — when a save pushes the org over its byte quota (`KICI_USER_CACHE_QUOTA_BYTES`, default 5 GiB), the oldest entries are evicted until the org is back under quota.
|
|
1817
|
+
- **TTL** — entries unused for `KICI_USER_CACHE_TTL_MS` (default 7 days) expire. The TTL refreshes on read (touch-on-read), so an actively used cache stays warm.
|
|
1818
|
+
|
|
1819
|
+
Both knobs are operator-configured on the orchestrator — see [orchestrator storage layout](../../operator/orchestrator/storage-layout.md).
|
|
1820
|
+
|
|
1821
|
+
## Observability
|
|
1822
|
+
|
|
1823
|
+
Each cache restore and save surfaces in the run timeline as a `cache:restore` / `cache:save` pseudo-step, reporting the outcome (hit/miss/saved, the matched key, bytes). The same outcomes are recorded as `cache.restore` / `cache.save` run events. See [data flows](../../architecture/data-flows.md#user-facing-cache-flow) for the restore/save protocol.
|
|
1824
|
+
|
|
1825
|
+
## See also
|
|
1826
|
+
|
|
1827
|
+
- [Core](./core.md) -- `job()` / `step()` factories the `cache` field attaches to
|
|
1828
|
+
- [Runtime](./runtime.md) -- `StepContext`, where `ctx.cache` lives
|
|
1829
|
+
- [Orchestrator storage layout](../../operator/orchestrator/storage-layout.md) -- cache prefix, quota, TTL, and eviction
|
|
1830
|
+
- [Data flows](../../architecture/data-flows.md#user-facing-cache-flow) -- restore/save protocol and trust→scope mapping
|
|
1831
|
+
|
|
1832
|
+
---
|
|
1833
|
+
|
|
1685
1834
|
## SDK reference: core
|
|
1686
1835
|
|
|
1687
|
-
Source: https://kici.dev/
|
|
1836
|
+
Source: https://docs.kici.dev/user/sdk/core/
|
|
1688
1837
|
|
|
1689
1838
|
## Factory functions
|
|
1690
1839
|
|
|
@@ -1698,21 +1847,22 @@ function workflow(name: string, options: WorkflowOptions): Workflow;
|
|
|
1698
1847
|
|
|
1699
1848
|
**Parameters:**
|
|
1700
1849
|
|
|
1701
|
-
| Parameter | Type | Required | Description
|
|
1702
|
-
| --------------------- | ---------------------------------------------------------------------- | -------- |
|
|
1703
|
-
| `name` | `string` | yes | Unique workflow name
|
|
1704
|
-
| `options.jobs` | `JobOrFactory[]` | yes | Static jobs and/or dynamic job generators
|
|
1705
|
-
| `options.on` | `Trigger \| Trigger[]` | no | When the workflow should trigger
|
|
1706
|
-
| `options.rules` | `Rule[]` | no | Conditions that must pass for execution
|
|
1707
|
-
| `options.description` | `string` | no | Human-readable description
|
|
1708
|
-
| `options.hashFiles` | `string[]` | no | Extra repo-relative paths or globs mixed into the workflow content hash. Changes invalidate the source cache.
|
|
1709
|
-
| `options.registries` | `Registry[]` | no | Private npm registries the agent authenticates against before `npm install`. Each `tokenSecret` uses qualified `<environment>:<secret>` syntax.
|
|
1710
|
-
| `options.installEnv` | `string[]` | no | Qualified `<environment>:<secret>` refs projected as env vars onto the install subprocess (used with a customer-committed `.kici/.npmrc`).
|
|
1711
|
-
| `options.onCancel` | `HookInput` | no | Runs when the workflow is cancelled
|
|
1712
|
-
| `options.cleanup` | `HookInput` | no | Always runs after the workflow (success, failure, or cancel)
|
|
1713
|
-
| `options.onSuccess` | `HookInput` | no | Runs on workflow success
|
|
1714
|
-
| `options.onFailure` | `HookInput` | no | Runs on workflow failure
|
|
1715
|
-
| `options.concurrency` | `{ group: (ctx) => string; cancelInProgress?: boolean; max?: number }` | no | Workflow-scoped concurrency. See [Concurrency](../concurrency.md).
|
|
1850
|
+
| Parameter | Type | Required | Description |
|
|
1851
|
+
| --------------------- | ---------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1852
|
+
| `name` | `string` | yes | Unique workflow name |
|
|
1853
|
+
| `options.jobs` | `JobOrFactory[]` | yes | Static jobs and/or dynamic job generators |
|
|
1854
|
+
| `options.on` | `Trigger \| Trigger[]` | no | When the workflow should trigger |
|
|
1855
|
+
| `options.rules` | `Rule[]` | no | Conditions that must pass for execution |
|
|
1856
|
+
| `options.description` | `string` | no | Human-readable description |
|
|
1857
|
+
| `options.hashFiles` | `string[]` | no | Extra repo-relative paths or globs mixed into the workflow content hash. Changes invalidate the source cache. |
|
|
1858
|
+
| `options.registries` | `Registry[]` | no | Private npm registries the agent authenticates against before `npm install`. Each `tokenSecret` uses qualified `<environment>:<secret>` syntax. |
|
|
1859
|
+
| `options.installEnv` | `string[]` | no | Qualified `<environment>:<secret>` refs projected as env vars onto the install subprocess (used with a customer-committed `.kici/.npmrc`). |
|
|
1860
|
+
| `options.onCancel` | `HookInput` | no | Runs when the workflow is cancelled |
|
|
1861
|
+
| `options.cleanup` | `HookInput` | no | Always runs after the workflow (success, failure, or cancel) |
|
|
1862
|
+
| `options.onSuccess` | `HookInput` | no | Runs on workflow success |
|
|
1863
|
+
| `options.onFailure` | `HookInput` | no | Runs on workflow failure |
|
|
1864
|
+
| `options.concurrency` | `{ group: (ctx) => string; cancelInProgress?: boolean; max?: number }` | no | Workflow-scoped concurrency. See [Concurrency](../concurrency.md). |
|
|
1865
|
+
| `options.timeout` | `number` | no | Whole-run wall-clock timeout in milliseconds across all jobs. On breach the orchestrator cancels the run and marks it timed out. See [Timeouts](#timeouts). |
|
|
1716
1866
|
|
|
1717
1867
|
**Returns:** `Workflow` -- an immutable workflow definition.
|
|
1718
1868
|
|
|
@@ -1738,45 +1888,47 @@ function job(options: JobOptions): Job;
|
|
|
1738
1888
|
|
|
1739
1889
|
**Parameters:**
|
|
1740
1890
|
|
|
1741
|
-
| Parameter | Type | Required | Description
|
|
1742
|
-
| -------------------------- | --------------------------------------------------------------- | -------------------- |
|
|
1743
|
-
| `name` | `string` | no | Job name (auto-generated UUID if omitted)
|
|
1744
|
-
| `options.runsOn` | `RunsOn` | yes | Runner label(s) and optional exclusions (see below)
|
|
1745
|
-
| `options.steps` | `StepInput[]` | yes (or use `run`) | Steps to execute in order. Mutually exclusive with `run`.
|
|
1746
|
-
| `options.run` | `(ctx) => Promise<unknown>` | yes (or use `steps`) | Single-step shorthand -- see [Single-step job shorthand](#single-step-job-shorthand). Mutually exclusive with `steps`.
|
|
1747
|
-
| `options.needs` | `NeedsEntry[]` | no | Job dependencies (must complete first) -- see [Job dependencies (`needs`)](#job-dependencies-needs)
|
|
1748
|
-
| `options.rules` | `Rule[]` | no | Conditions for conditional execution
|
|
1749
|
-
| `options.description` | `string` | no | Human-readable description
|
|
1750
|
-
| `options.matrix` | `Matrix` | no | Matrix configuration for job expansion
|
|
1751
|
-
| `options.include` | `MatrixInclude[]` | no | Additional matrix combinations
|
|
1752
|
-
| `options.exclude` | `MatrixExclude[]` | no | Matrix combinations to remove
|
|
1753
|
-
| `options.checkout` | `boolean` | no (default: `true`) | When `false`, agent skips git clone. Useful for deploy/notify jobs.
|
|
1754
|
-
| `options.container` | `string \| ContainerConfig` | no | Docker image for job execution. String form is the image name; object form adds `env`. All steps run inside the container.
|
|
1755
|
-
| `options.environment` | `string \| ((event) => string \| Promise<string>)` | no | Deployment environment for this job. Static string or async/dynamic function -- see [Dynamic values](../dynamic-values.md).
|
|
1756
|
-
| `options.env` | `Record<string, string> \| ((event) => Record<string, string>)` | no | Environment variables. Static object or async/dynamic function -- see [Dynamic values](../dynamic-values.md).
|
|
1757
|
-
| `options.concurrencyGroup` | `string \| ((event) => string \| Promise<string>)` | no | Concurrency group name (defaults to environment name) -- see [Concurrency](../concurrency.md).
|
|
1758
|
-
| `options.onCancel` | `HookInput` | no | Hook that runs when the job is cancelled
|
|
1759
|
-
| `options.cleanup` | `HookInput` | no | Hook that always runs after completion
|
|
1760
|
-
| `options.onSuccess` | `HookInput` | no | Hook that runs when the job succeeds
|
|
1761
|
-
| `options.onFailure` | `HookInput` | no | Hook that runs when the job fails
|
|
1762
|
-
| `options.beforeStep` | `HookInput` | no | Hook that runs before each step
|
|
1763
|
-
| `options.afterStep` | `HookInput` | no | Hook that runs after each step
|
|
1764
|
-
| `options.gracePeriod` | `number` | no | Seconds before SIGKILL after SIGTERM during cancellation -- see [Hooks](../hooks.md#hook-timeout).
|
|
1765
|
-
| `options.
|
|
1891
|
+
| Parameter | Type | Required | Description |
|
|
1892
|
+
| -------------------------- | --------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1893
|
+
| `name` | `string` | no | Job name (auto-generated UUID if omitted) |
|
|
1894
|
+
| `options.runsOn` | `RunsOn` | yes | Runner label(s) and optional exclusions (see below) |
|
|
1895
|
+
| `options.steps` | `StepInput[]` | yes (or use `run`) | Steps to execute in order. Mutually exclusive with `run`. |
|
|
1896
|
+
| `options.run` | `(ctx) => Promise<unknown>` | yes (or use `steps`) | Single-step shorthand -- see [Single-step job shorthand](#single-step-job-shorthand). Mutually exclusive with `steps`. |
|
|
1897
|
+
| `options.needs` | `NeedsEntry[]` | no | Job dependencies (must complete first) -- see [Job dependencies (`needs`)](#job-dependencies-needs) |
|
|
1898
|
+
| `options.rules` | `Rule[]` | no | Conditions for conditional execution |
|
|
1899
|
+
| `options.description` | `string` | no | Human-readable description |
|
|
1900
|
+
| `options.matrix` | `Matrix` | no | Matrix configuration for job expansion |
|
|
1901
|
+
| `options.include` | `MatrixInclude[]` | no | Additional matrix combinations |
|
|
1902
|
+
| `options.exclude` | `MatrixExclude[]` | no | Matrix combinations to remove |
|
|
1903
|
+
| `options.checkout` | `boolean` | no (default: `true`) | When `false`, agent skips git clone. Useful for deploy/notify jobs. |
|
|
1904
|
+
| `options.container` | `string \| ContainerConfig` | no | Docker image for job execution. String form is the image name; object form adds `env`. All steps run inside the container. |
|
|
1905
|
+
| `options.environment` | `string \| ((event) => string \| Promise<string>)` | no | Deployment environment for this job. Static string or async/dynamic function -- see [Dynamic values](../dynamic-values.md). |
|
|
1906
|
+
| `options.env` | `Record<string, string> \| ((event) => Record<string, string>)` | no | Environment variables. Static object or async/dynamic function -- see [Dynamic values](../dynamic-values.md). |
|
|
1907
|
+
| `options.concurrencyGroup` | `string \| ((event) => string \| Promise<string>)` | no | Concurrency group name (defaults to environment name) -- see [Concurrency](../concurrency.md). |
|
|
1908
|
+
| `options.onCancel` | `HookInput` | no | Hook that runs when the job is cancelled |
|
|
1909
|
+
| `options.cleanup` | `HookInput` | no | Hook that always runs after completion |
|
|
1910
|
+
| `options.onSuccess` | `HookInput` | no | Hook that runs when the job succeeds |
|
|
1911
|
+
| `options.onFailure` | `HookInput` | no | Hook that runs when the job fails |
|
|
1912
|
+
| `options.beforeStep` | `HookInput` | no | Hook that runs before each step |
|
|
1913
|
+
| `options.afterStep` | `HookInput` | no | Hook that runs after each step |
|
|
1914
|
+
| `options.gracePeriod` | `number` | no | Seconds before SIGKILL after SIGTERM during cancellation -- see [Hooks](../hooks.md#hook-timeout). |
|
|
1915
|
+
| `options.timeout` | `number` | no | Total job wall-clock timeout in milliseconds (init + all steps + hooks). On breach the job is aborted and reported timed out. See [Timeouts](#timeouts). |
|
|
1916
|
+
| `options.resources` | `ResourceRequest` | no | Per-job CPU / memory request and limit. See [Per-job resources](#per-job-resources) below. |
|
|
1917
|
+
| `options.init` | `GenericInitConfig \| GenericInitConfig[] \| false` | no | Per-job initialization run after clone, before steps -- provisions a toolchain. See [Per-job init](#per-job-init) below. |
|
|
1766
1918
|
|
|
1767
1919
|
**Returns:** `Job` -- an immutable job definition.
|
|
1768
1920
|
|
|
1769
1921
|
```typescript
|
|
1770
1922
|
// Named job
|
|
1771
1923
|
const build = job('build', {
|
|
1772
|
-
runsOn: '
|
|
1924
|
+
runsOn: 'linux',
|
|
1773
1925
|
steps: [checkout, install, compile],
|
|
1774
1926
|
needs: [lint],
|
|
1775
1927
|
});
|
|
1776
1928
|
|
|
1777
1929
|
// Anonymous job (auto-generated UUID name)
|
|
1778
1930
|
const build = job({
|
|
1779
|
-
runsOn: '
|
|
1931
|
+
runsOn: 'linux',
|
|
1780
1932
|
steps: [checkout, install],
|
|
1781
1933
|
});
|
|
1782
1934
|
```
|
|
@@ -1802,7 +1954,7 @@ runsOn: { labels: ['linux', 'docker'], exclude: ['gpu'] }
|
|
|
1802
1954
|
- **Required labels:** The agent must have every label in the `labels` array (or the string/array form).
|
|
1803
1955
|
- **Excluded labels:** The agent must NOT have any label in the `exclude` array. This includes auto-derived labels like `kici:arch:arm64`, `kici:os:linux`, etc.
|
|
1804
1956
|
- **Compile-time validation:** The compiler will error if any label appears in both `labels` and `exclude` (overlap detection).
|
|
1805
|
-
- **Operator-declared mandatory labels:** Operators may mark a scaler with `mandatoryLabels` (Kubernetes-taint-style opt-in). When a scaler declares a mandatory label, a job is only allowed to land on it if `runsOn.labels` includes that label. A workflow targeting such a scaler must explicitly list the mandatory label in `runsOn`. See the [auto-scaler
|
|
1957
|
+
- **Operator-declared mandatory labels:** Operators may mark a scaler with `mandatoryLabels` (Kubernetes-taint-style opt-in). When a scaler declares a mandatory label, a job is only allowed to land on it if `runsOn.labels` includes that label. A workflow targeting such a scaler must explicitly list the mandatory label in `runsOn`. See the [auto-scaler mandatory labels](../../operator/orchestrator/auto-scaler/common-config.md#mandatory--exclude-labels) for details.
|
|
1806
1958
|
|
|
1807
1959
|
```typescript
|
|
1808
1960
|
// Route to any Linux agent that does NOT have the 'gpu' label
|
|
@@ -1899,6 +2051,65 @@ Per-backend kernel enforcement of `limits`:
|
|
|
1899
2051
|
- **Firecracker backend:** always enforced. Fractional CPU rounds up to the nearest integer vCPU.
|
|
1900
2052
|
- **Bare-metal backend:** advisory by default — the scaler caps still apply, but no cgroup is created. Operators can opt in to kernel enforcement via `enforceCgroups: true` on the scaler entry.
|
|
1901
2053
|
|
|
2054
|
+
### Per-job init
|
|
2055
|
+
|
|
2056
|
+
`options.init` declares a hand-written command that runs **after the repo is cloned and before the job's steps execute**. Its purpose is to provision a repo-declared toolchain (a `mise` toolchain, a custom setup script, a language runtime) and put it on the environment every subsequent step sees.
|
|
2057
|
+
|
|
2058
|
+
```typescript
|
|
2059
|
+
import { workflow, job, step, push } from '@kici-dev/sdk';
|
|
2060
|
+
|
|
2061
|
+
export const build = workflow('build', {
|
|
2062
|
+
on: [push()],
|
|
2063
|
+
jobs: [
|
|
2064
|
+
job('build', {
|
|
2065
|
+
runsOn: 'linux',
|
|
2066
|
+
init: {
|
|
2067
|
+
run: `
|
|
2068
|
+
set -euo pipefail
|
|
2069
|
+
command -v mise >/dev/null || curl -fsSL https://mise.run | sh
|
|
2070
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
2071
|
+
mise install
|
|
2072
|
+
mise env -s bash | sed -n 's/^export //p' >> "$KICI_ENV"
|
|
2073
|
+
echo "$HOME/.local/share/mise/shims" >> "$KICI_PATH"
|
|
2074
|
+
`,
|
|
2075
|
+
cache: { key: 'mise-jq-1.7.1', paths: ['~/.local/share/mise'] },
|
|
2076
|
+
timeout: 600_000,
|
|
2077
|
+
},
|
|
2078
|
+
steps: [
|
|
2079
|
+
step('show-jq-version', async (ctx) => {
|
|
2080
|
+
// jq is on PATH because the init phase appended the mise shims dir to $KICI_PATH.
|
|
2081
|
+
const { stdout } = await ctx.$`jq --version`;
|
|
2082
|
+
ctx.log.info(`jq version: ${stdout.trim()}`);
|
|
2083
|
+
}),
|
|
2084
|
+
],
|
|
2085
|
+
}),
|
|
2086
|
+
],
|
|
2087
|
+
});
|
|
2088
|
+
```
|
|
2089
|
+
|
|
2090
|
+
**`GenericInitConfig` shape:**
|
|
2091
|
+
|
|
2092
|
+
| Field | Type | Required | Description |
|
|
2093
|
+
| --------- | ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2094
|
+
| `run` | `string` | yes | Command run after clone, before steps. Runs in the job's sandbox at the clone root. Must be a non-empty command. |
|
|
2095
|
+
| `shell` | `string` | no | Shell used to run `run`. Defaults to `bash`. |
|
|
2096
|
+
| `cache` | `CacheSpec` | no | Cache spec for binaries the command installs -- restored before the command, saved after on a key miss. See [Caching](./caching.md). |
|
|
2097
|
+
| `timeout` | `number` | no | Max wall-clock for this init command in milliseconds. Defaults to 10 minutes. On breach the init is aborted and the job is reported timed out. |
|
|
2098
|
+
| `env` | `Record<string,string>` | no | Static environment variables available to the command. |
|
|
2099
|
+
|
|
2100
|
+
**The `$KICI_ENV` / `$KICI_PATH` handoff.** The init command does not mutate the step environment directly. Instead it writes what it wants visible to later steps to two files the agent allocates and exposes as environment variables:
|
|
2101
|
+
|
|
2102
|
+
- **`$KICI_ENV`** -- append one `KEY=value` line per environment variable. The agent reads the file after the command and makes each variable available to every subsequent step.
|
|
2103
|
+
- **`$KICI_PATH`** -- append one directory per line. The agent prepends each directory to `PATH` for every subsequent step.
|
|
2104
|
+
|
|
2105
|
+
The agent reads both files after the command succeeds, applies the delta, and the resulting environment is visible to all steps that follow (and to any later init command).
|
|
2106
|
+
|
|
2107
|
+
**Failure before steps.** If the init command exits non-zero or exceeds its `timeout`, the job **fails before any step runs** -- the init surfaces as a failed `init:<n>` pseudo-step in the run timeline (alongside the step list), its logs are attached, and the step loop never executes. This makes a broken toolchain a clear, early failure rather than a confusing mid-run error.
|
|
2108
|
+
|
|
2109
|
+
**Arrays run in order.** Passing `GenericInitConfig[]` runs the inits sequentially; each one's `$KICI_ENV` / `$KICI_PATH` delta is applied before the next runs, so a later init sees an earlier init's tools on `PATH`. The first init to fail stops the sequence and fails the job.
|
|
2110
|
+
|
|
2111
|
+
**`init: false`** is an explicit opt-out (reserved for a future auto-detect layer); it behaves the same as omitting `init`.
|
|
2112
|
+
|
|
1902
2113
|
## Step & job authoring patterns
|
|
1903
2114
|
|
|
1904
2115
|
KiCI supports several authoring patterns for steps and jobs to reduce boilerplate and improve developer experience.
|
|
@@ -1997,6 +2208,44 @@ const deploy = job('deploy', {
|
|
|
1997
2208
|
|
|
1998
2209
|
The `run` function is stored as the job's only step with an auto-generated name (`step-1`). `run` and `steps` are mutually exclusive -- providing both throws an error.
|
|
1999
2210
|
|
|
2211
|
+
### Timeouts
|
|
2212
|
+
|
|
2213
|
+
`timeout` (milliseconds) can be set at three levels. Each level caps **its own scope** independently — a workflow or job timeout is a separate wall-clock cap, **not** a default that flows down to steps.
|
|
2214
|
+
|
|
2215
|
+
| Level | Field | Caps | Enforced by | On breach |
|
|
2216
|
+
| ------------ | ---------------------------- | ------------------------------------------------------ | ---------------- | ----------------------------------------------------------------- |
|
|
2217
|
+
| **step** | `step(..., { timeout })` | A single step's wall-clock. | the agent | The step fails; falls back to the 30-minute default when unset. |
|
|
2218
|
+
| **job** | `job(..., { timeout })` | The job's total wall-clock (init + all steps + hooks). | the agent | The job is aborted and reported failed with a "timed out" reason. |
|
|
2219
|
+
| **workflow** | `workflow(..., { timeout })` | The whole run's wall-clock across all jobs. | the orchestrator | The run is cancelled with a "timed out" reason. |
|
|
2220
|
+
|
|
2221
|
+
```typescript
|
|
2222
|
+
export default workflow('ci', {
|
|
2223
|
+
timeout: 1_800_000, // whole run must finish within 30 minutes
|
|
2224
|
+
jobs: [
|
|
2225
|
+
job('build', {
|
|
2226
|
+
runsOn: 'linux',
|
|
2227
|
+
timeout: 600_000, // this job (init + steps + hooks) within 10 minutes
|
|
2228
|
+
steps: [
|
|
2229
|
+
step('compile', {
|
|
2230
|
+
timeout: 120_000, // this single step within 2 minutes
|
|
2231
|
+
run: async (ctx) => {
|
|
2232
|
+
await ctx.$`make build`;
|
|
2233
|
+
},
|
|
2234
|
+
}),
|
|
2235
|
+
],
|
|
2236
|
+
}),
|
|
2237
|
+
],
|
|
2238
|
+
});
|
|
2239
|
+
```
|
|
2240
|
+
|
|
2241
|
+
**Precedence — each scope caps its own scope.** The three timeouts are independent caps, not a fallback chain:
|
|
2242
|
+
|
|
2243
|
+
- A **step** with no `timeout` falls back to the 30-minute agent default, regardless of the job or workflow timeout. A job timeout never becomes a step's default.
|
|
2244
|
+
- A **job** `timeout` bounds the job's total wall-clock (its init, every step including their own per-step timeouts, and its hooks). It does not change any step's individual cap.
|
|
2245
|
+
- A **workflow** `timeout` is a run-level deadline. The orchestrator records it when the run starts and cancels the run if its wall-clock exceeds the timeout, even when individual jobs and steps are still within their own caps.
|
|
2246
|
+
|
|
2247
|
+
Workflow and job timeouts surface with a distinct "timed out" reason so the dashboard labels the run or job as timed out rather than a generic failure or cancel.
|
|
2248
|
+
|
|
2000
2249
|
### Output chaining
|
|
2001
2250
|
|
|
2002
2251
|
Steps and jobs can access outputs from preceding steps/jobs using two patterns.
|
|
@@ -2169,9 +2418,357 @@ These IDs are stable as long as the order of unnamed entries does not change. Ad
|
|
|
2169
2418
|
|
|
2170
2419
|
---
|
|
2171
2420
|
|
|
2421
|
+
## Event payload reference
|
|
2422
|
+
|
|
2423
|
+
Source: https://docs.kici.dev/user/sdk/event-payloads/
|
|
2424
|
+
|
|
2425
|
+
<!-- Generated by scripts/docs-gen-event-payloads.ts — do not edit by hand. Regenerate: pnpm docs:gen:events -->
|
|
2426
|
+
|
|
2427
|
+
## The envelope
|
|
2428
|
+
|
|
2429
|
+
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.
|
|
2430
|
+
|
|
2431
|
+
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.
|
|
2432
|
+
|
|
2433
|
+
These fields are present on every envelope (the `EventBase` shape):
|
|
2434
|
+
|
|
2435
|
+
| Field | Type | Description |
|
|
2436
|
+
| ----------------- | ------------------------- | ------------------------------------------------------------------------------- |
|
|
2437
|
+
| `type` | `string` | Normalized event type discriminant. |
|
|
2438
|
+
| `action?` | `string` | Sub-action (e.g. 'opened', 'created', 'submitted'). |
|
|
2439
|
+
| `targetBranch?` | `string` | Target branch (push target, PR base, or default branch). |
|
|
2440
|
+
| `sourceBranch?` | `string` | Source branch (PR head branch). Only set for PR-like events. |
|
|
2441
|
+
| `provider?` | `string` | Provider that originated this event. |
|
|
2442
|
+
| `isForkPR?` | `boolean` | Whether this PR comes from a fork. Only set for PR-like events. |
|
|
2443
|
+
| `baseBranch?` | `string` | Base branch ref for PR events. |
|
|
2444
|
+
| `senderUsername?` | `string` | Sender username from the webhook payload. |
|
|
2445
|
+
| `sourceRepo?` | `string` | Repository identifier (e.g. "owner/repo"). |
|
|
2446
|
+
| `changedFiles?` | `string[]` | Files changed in this event (for path filtering). |
|
|
2447
|
+
| `payload?` | `Record<string, unknown>` | Raw webhook payload from the provider. May be absent in flattened event forms. |
|
|
2448
|
+
| `[key: string]` | `unknown` | Index signature for backward compatibility — untyped fields resolve to unknown. |
|
|
2449
|
+
|
|
2450
|
+
## Event types
|
|
2451
|
+
|
|
2452
|
+
One section per member of the `EventPayload` union. The heading is the `type` literal; the table lists the fields of that event's `payload` property when it declares a typed shape.
|
|
2453
|
+
|
|
2454
|
+
### `pull_request`
|
|
2455
|
+
|
|
2456
|
+
Carried by `PullRequestEventPayload`. The `payload` property has the following shape:
|
|
2457
|
+
|
|
2458
|
+
| Field | Type | Description |
|
|
2459
|
+
| --------------- | ------------------- | ----------- |
|
|
2460
|
+
| `action` | `string` | |
|
|
2461
|
+
| `number` | `number` | |
|
|
2462
|
+
| `pull_request` | `GitHubPullRequest` | |
|
|
2463
|
+
| `repository` | `GitHubRepository` | |
|
|
2464
|
+
| `sender` | `GitHubUser` | |
|
|
2465
|
+
| `[key: string]` | `unknown` | |
|
|
2466
|
+
|
|
2467
|
+
### `push`
|
|
2468
|
+
|
|
2469
|
+
Carried by `PushEventPayload`. The `payload` property has the following shape:
|
|
2470
|
+
|
|
2471
|
+
| Field | Type | Description |
|
|
2472
|
+
| --------------- | ------------------ | ----------- |
|
|
2473
|
+
| `ref` | `string` | |
|
|
2474
|
+
| `after` | `string` | |
|
|
2475
|
+
| `before` | `string` | |
|
|
2476
|
+
| `head_commit?` | `GitHubCommit` | |
|
|
2477
|
+
| `commits?` | `GitHubCommit[]` | |
|
|
2478
|
+
| `repository` | `GitHubRepository` | |
|
|
2479
|
+
| `sender?` | `GitHubUser` | |
|
|
2480
|
+
| `forced?` | `boolean` | |
|
|
2481
|
+
| `[key: string]` | `unknown` | |
|
|
2482
|
+
|
|
2483
|
+
### `tag`
|
|
2484
|
+
|
|
2485
|
+
Carried by `TagEventPayload`. The `payload` property has the following shape:
|
|
2486
|
+
|
|
2487
|
+
| Field | Type | Description |
|
|
2488
|
+
| --------------- | ------------------ | ----------- |
|
|
2489
|
+
| `ref` | `string` | |
|
|
2490
|
+
| `after` | `string` | |
|
|
2491
|
+
| `repository` | `GitHubRepository` | |
|
|
2492
|
+
| `sender?` | `GitHubUser` | |
|
|
2493
|
+
| `[key: string]` | `unknown` | |
|
|
2494
|
+
|
|
2495
|
+
### `comment`
|
|
2496
|
+
|
|
2497
|
+
Carried by `CommentEventPayload`. The `payload` property has the following shape:
|
|
2498
|
+
|
|
2499
|
+
| Field | Type | Description |
|
|
2500
|
+
| --------------- | ------------------------------------------------------------------------------------ | ----------- |
|
|
2501
|
+
| `action` | `string` | |
|
|
2502
|
+
| `comment` | `GitHubComment` | |
|
|
2503
|
+
| `issue?` | `{ number: number; title?: string; pull_request?: unknown; [key: string]: unknown }` | |
|
|
2504
|
+
| `repository` | `GitHubRepository` | |
|
|
2505
|
+
| `sender` | `GitHubUser` | |
|
|
2506
|
+
| `[key: string]` | `unknown` | |
|
|
2507
|
+
|
|
2508
|
+
### `review`
|
|
2509
|
+
|
|
2510
|
+
Carried by `ReviewEventPayload`. The `payload` property has the following shape:
|
|
2511
|
+
|
|
2512
|
+
| Field | Type | Description |
|
|
2513
|
+
| --------------- | ------------------- | ----------- |
|
|
2514
|
+
| `action` | `string` | |
|
|
2515
|
+
| `review` | `GitHubReview` | |
|
|
2516
|
+
| `pull_request` | `GitHubPullRequest` | |
|
|
2517
|
+
| `repository` | `GitHubRepository` | |
|
|
2518
|
+
| `sender` | `GitHubUser` | |
|
|
2519
|
+
| `[key: string]` | `unknown` | |
|
|
2520
|
+
|
|
2521
|
+
### `review_comment`
|
|
2522
|
+
|
|
2523
|
+
Carried by `ReviewCommentEventPayload`. The `payload` property has the following shape:
|
|
2524
|
+
|
|
2525
|
+
| Field | Type | Description |
|
|
2526
|
+
| --------------- | ------------------- | ----------- |
|
|
2527
|
+
| `action` | `string` | |
|
|
2528
|
+
| `comment` | `GitHubComment` | |
|
|
2529
|
+
| `pull_request` | `GitHubPullRequest` | |
|
|
2530
|
+
| `repository` | `GitHubRepository` | |
|
|
2531
|
+
| `sender` | `GitHubUser` | |
|
|
2532
|
+
| `[key: string]` | `unknown` | |
|
|
2533
|
+
|
|
2534
|
+
### `release`
|
|
2535
|
+
|
|
2536
|
+
Carried by `ReleaseEventPayload`. The `payload` property has the following shape:
|
|
2537
|
+
|
|
2538
|
+
| Field | Type | Description |
|
|
2539
|
+
| --------------- | ------------------ | ----------- |
|
|
2540
|
+
| `action` | `string` | |
|
|
2541
|
+
| `release` | `GitHubRelease` | |
|
|
2542
|
+
| `repository` | `GitHubRepository` | |
|
|
2543
|
+
| `sender` | `GitHubUser` | |
|
|
2544
|
+
| `[key: string]` | `unknown` | |
|
|
2545
|
+
|
|
2546
|
+
### `dispatch`
|
|
2547
|
+
|
|
2548
|
+
Carried by `DispatchEventPayload`. The `payload` property has the following shape:
|
|
2549
|
+
|
|
2550
|
+
| Field | Type | Description |
|
|
2551
|
+
| ----------------- | ------------------------- | ----------- |
|
|
2552
|
+
| `action` | `string` | |
|
|
2553
|
+
| `client_payload?` | `Record<string, unknown>` | |
|
|
2554
|
+
| `repository` | `GitHubRepository` | |
|
|
2555
|
+
| `sender?` | `GitHubUser` | |
|
|
2556
|
+
| `[key: string]` | `unknown` | |
|
|
2557
|
+
|
|
2558
|
+
### `create`
|
|
2559
|
+
|
|
2560
|
+
Carried by `CreateEventPayload`. The `payload` property has the following shape:
|
|
2561
|
+
|
|
2562
|
+
| Field | Type | Description |
|
|
2563
|
+
| --------------- | ------------------ | ----------- |
|
|
2564
|
+
| `ref` | `string` | |
|
|
2565
|
+
| `ref_type` | `string` | |
|
|
2566
|
+
| `repository` | `GitHubRepository` | |
|
|
2567
|
+
| `sender` | `GitHubUser` | |
|
|
2568
|
+
| `[key: string]` | `unknown` | |
|
|
2569
|
+
|
|
2570
|
+
### `delete`
|
|
2571
|
+
|
|
2572
|
+
Carried by `DeleteEventPayload`. The `payload` property has the following shape:
|
|
2573
|
+
|
|
2574
|
+
| Field | Type | Description |
|
|
2575
|
+
| --------------- | ------------------ | ----------- |
|
|
2576
|
+
| `ref` | `string` | |
|
|
2577
|
+
| `ref_type` | `string` | |
|
|
2578
|
+
| `repository` | `GitHubRepository` | |
|
|
2579
|
+
| `sender` | `GitHubUser` | |
|
|
2580
|
+
| `[key: string]` | `unknown` | |
|
|
2581
|
+
|
|
2582
|
+
### `status`
|
|
2583
|
+
|
|
2584
|
+
Carried by `StatusEventPayload`. The `payload` property has the following shape:
|
|
2585
|
+
|
|
2586
|
+
| Field | Type | Description |
|
|
2587
|
+
| --------------- | ------------------------------------------------- | ----------- |
|
|
2588
|
+
| `state` | `string` | |
|
|
2589
|
+
| `sha` | `string` | |
|
|
2590
|
+
| `context` | `string` | |
|
|
2591
|
+
| `description?` | `string` | |
|
|
2592
|
+
| `target_url?` | `string` | |
|
|
2593
|
+
| `branches?` | `Array<{ name: string; [key: string]: unknown }>` | |
|
|
2594
|
+
| `repository` | `GitHubRepository` | |
|
|
2595
|
+
| `sender` | `GitHubUser` | |
|
|
2596
|
+
| `[key: string]` | `unknown` | |
|
|
2597
|
+
|
|
2598
|
+
### `workflow_run`
|
|
2599
|
+
|
|
2600
|
+
Carried by `WorkflowRunEventPayload`. The `payload` property has the following shape:
|
|
2601
|
+
|
|
2602
|
+
| Field | Type | Description |
|
|
2603
|
+
| --------------- | ------------------------------------------------------------------------------------------------------ | ----------- |
|
|
2604
|
+
| `action` | `string` | |
|
|
2605
|
+
| `workflow_run` | `{ head_branch: string; name: string; conclusion?: string; status?: string; [key: string]: unknown; }` | |
|
|
2606
|
+
| `repository` | `GitHubRepository` | |
|
|
2607
|
+
| `sender` | `GitHubUser` | |
|
|
2608
|
+
| `[key: string]` | `unknown` | |
|
|
2609
|
+
|
|
2610
|
+
### `fork`
|
|
2611
|
+
|
|
2612
|
+
Carried by `ForkEventPayload`. The `payload` property has the following shape:
|
|
2613
|
+
|
|
2614
|
+
| Field | Type | Description |
|
|
2615
|
+
| --------------- | ----------------------------------------------- | ----------- |
|
|
2616
|
+
| `forkee` | `{ full_name: string; [key: string]: unknown }` | |
|
|
2617
|
+
| `repository` | `GitHubRepository` | |
|
|
2618
|
+
| `sender` | `GitHubUser` | |
|
|
2619
|
+
| `[key: string]` | `unknown` | |
|
|
2620
|
+
|
|
2621
|
+
### `star`
|
|
2622
|
+
|
|
2623
|
+
Carried by `StarEventPayload`. The `payload` property has the following shape:
|
|
2624
|
+
|
|
2625
|
+
| Field | Type | Description |
|
|
2626
|
+
| --------------- | ------------------ | ----------- |
|
|
2627
|
+
| `action` | `string` | |
|
|
2628
|
+
| `repository` | `GitHubRepository` | |
|
|
2629
|
+
| `sender` | `GitHubUser` | |
|
|
2630
|
+
| `[key: string]` | `unknown` | |
|
|
2631
|
+
|
|
2632
|
+
### `watch`
|
|
2633
|
+
|
|
2634
|
+
Carried by `WatchEventPayload`. The `payload` property has the following shape:
|
|
2635
|
+
|
|
2636
|
+
| Field | Type | Description |
|
|
2637
|
+
| --------------- | ------------------ | ----------- |
|
|
2638
|
+
| `action` | `string` | |
|
|
2639
|
+
| `repository` | `GitHubRepository` | |
|
|
2640
|
+
| `sender` | `GitHubUser` | |
|
|
2641
|
+
| `[key: string]` | `unknown` | |
|
|
2642
|
+
|
|
2643
|
+
### `webhook`
|
|
2644
|
+
|
|
2645
|
+
Carried by `WebhookEventPayload`. Adds no typed fields beyond the shared envelope; `payload` is the raw provider body (`Record<string, unknown>`).
|
|
2646
|
+
|
|
2647
|
+
### `kici_event`
|
|
2648
|
+
|
|
2649
|
+
Carried by `KiciEventPayload`. Adds no typed fields beyond the shared envelope; `payload` is the raw provider body (`Record<string, unknown>`).
|
|
2650
|
+
|
|
2651
|
+
### `workflow_complete`
|
|
2652
|
+
|
|
2653
|
+
Carried by `WorkflowCompleteEventPayload`. Adds no typed fields beyond the shared envelope; `payload` is the raw provider body (`Record<string, unknown>`).
|
|
2654
|
+
|
|
2655
|
+
### `job_complete`
|
|
2656
|
+
|
|
2657
|
+
Carried by `JobCompleteEventPayload`. Adds no typed fields beyond the shared envelope; `payload` is the raw provider body (`Record<string, unknown>`).
|
|
2658
|
+
|
|
2659
|
+
### `generic_webhook`
|
|
2660
|
+
|
|
2661
|
+
Carried by `GenericWebhookEventPayload`. Adds no typed fields beyond the shared envelope; `payload` is the raw provider body (`Record<string, unknown>`).
|
|
2662
|
+
|
|
2663
|
+
### `schedule`
|
|
2664
|
+
|
|
2665
|
+
Carried by `ScheduleEventPayload`. Adds no typed fields beyond the shared envelope; `payload` is the raw provider body (`Record<string, unknown>`).
|
|
2666
|
+
|
|
2667
|
+
### `lifecycle`
|
|
2668
|
+
|
|
2669
|
+
Carried by `LifecycleEventPayload`. Adds no typed fields beyond the shared envelope; `payload` is the raw provider body (`Record<string, unknown>`).
|
|
2670
|
+
|
|
2671
|
+
### `rerun`
|
|
2672
|
+
|
|
2673
|
+
Carried by `RerunEventPayload`. Adds no typed fields beyond the shared envelope; `payload` is the raw provider body (`Record<string, unknown>`).
|
|
2674
|
+
|
|
2675
|
+
### `manual_schedule`
|
|
2676
|
+
|
|
2677
|
+
Carried by `ManualScheduleEventPayload`. Adds no typed fields beyond the shared envelope; `payload` is the raw provider body (`Record<string, unknown>`).
|
|
2678
|
+
|
|
2679
|
+
### `unknown`
|
|
2680
|
+
|
|
2681
|
+
Carried by `UnknownEventPayload`. Adds no typed fields beyond the shared envelope; `payload` is the raw provider body (`Record<string, unknown>`).
|
|
2682
|
+
|
|
2683
|
+
## Shared GitHub object shapes
|
|
2684
|
+
|
|
2685
|
+
The typed `payload` shapes above reference these partial GitHub object types. Each lists only the commonly accessed fields; the index signature on every shape resolves any other field to `unknown`.
|
|
2686
|
+
|
|
2687
|
+
### `GitHubRepository`
|
|
2688
|
+
|
|
2689
|
+
| Field | Type | Description |
|
|
2690
|
+
| ---------------- | ------------------------------------------- | ----------- |
|
|
2691
|
+
| `full_name` | `string` | |
|
|
2692
|
+
| `default_branch` | `string` | |
|
|
2693
|
+
| `name?` | `string` | |
|
|
2694
|
+
| `owner?` | `{ login: string; [key: string]: unknown }` | |
|
|
2695
|
+
| `private?` | `boolean` | |
|
|
2696
|
+
| `[key: string]` | `unknown` | |
|
|
2697
|
+
|
|
2698
|
+
### `GitHubUser`
|
|
2699
|
+
|
|
2700
|
+
| Field | Type | Description |
|
|
2701
|
+
| --------------- | --------- | ----------- |
|
|
2702
|
+
| `login` | `string` | |
|
|
2703
|
+
| `id?` | `number` | |
|
|
2704
|
+
| `[key: string]` | `unknown` | |
|
|
2705
|
+
|
|
2706
|
+
### `GitHubPullRequest`
|
|
2707
|
+
|
|
2708
|
+
| Field | Type | Description |
|
|
2709
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------- | ----------- |
|
|
2710
|
+
| `number` | `number` | |
|
|
2711
|
+
| `draft?` | `boolean` | |
|
|
2712
|
+
| `title?` | `string` | |
|
|
2713
|
+
| `body?` | `string` | |
|
|
2714
|
+
| `state?` | `string` | |
|
|
2715
|
+
| `merged?` | `boolean` | |
|
|
2716
|
+
| `head` | `{ ref: string; sha: string; repo?: { full_name: string; [key: string]: unknown }; [key: string]: unknown; }` | |
|
|
2717
|
+
| `base` | `{ ref: string; repo?: { full_name: string; [key: string]: unknown }; [key: string]: unknown; }` | |
|
|
2718
|
+
| `user?` | `GitHubUser` | |
|
|
2719
|
+
| `labels?` | `Array<{ name: string; [key: string]: unknown }>` | |
|
|
2720
|
+
| `[key: string]` | `unknown` | |
|
|
2721
|
+
|
|
2722
|
+
### `GitHubCommit`
|
|
2723
|
+
|
|
2724
|
+
| Field | Type | Description |
|
|
2725
|
+
| --------------- | ------------------------------------------------------------------------------ | ----------- |
|
|
2726
|
+
| `id` | `string` | |
|
|
2727
|
+
| `message` | `string` | |
|
|
2728
|
+
| `author?` | `{ name?: string; email?: string; username?: string; [key: string]: unknown }` | |
|
|
2729
|
+
| `timestamp?` | `string` | |
|
|
2730
|
+
| `added?` | `string[]` | |
|
|
2731
|
+
| `removed?` | `string[]` | |
|
|
2732
|
+
| `modified?` | `string[]` | |
|
|
2733
|
+
| `[key: string]` | `unknown` | |
|
|
2734
|
+
|
|
2735
|
+
### `GitHubComment`
|
|
2736
|
+
|
|
2737
|
+
| Field | Type | Description |
|
|
2738
|
+
| --------------- | ------------ | ----------- |
|
|
2739
|
+
| `id` | `number` | |
|
|
2740
|
+
| `body` | `string` | |
|
|
2741
|
+
| `user` | `GitHubUser` | |
|
|
2742
|
+
| `[key: string]` | `unknown` | |
|
|
2743
|
+
|
|
2744
|
+
### `GitHubReview`
|
|
2745
|
+
|
|
2746
|
+
| Field | Type | Description |
|
|
2747
|
+
| --------------- | ------------ | ----------- |
|
|
2748
|
+
| `id` | `number` | |
|
|
2749
|
+
| `state` | `string` | |
|
|
2750
|
+
| `body?` | `string` | |
|
|
2751
|
+
| `user` | `GitHubUser` | |
|
|
2752
|
+
| `[key: string]` | `unknown` | |
|
|
2753
|
+
|
|
2754
|
+
### `GitHubRelease`
|
|
2755
|
+
|
|
2756
|
+
| Field | Type | Description |
|
|
2757
|
+
| ------------------- | --------- | ----------- |
|
|
2758
|
+
| `id` | `number` | |
|
|
2759
|
+
| `tag_name` | `string` | |
|
|
2760
|
+
| `name?` | `string` | |
|
|
2761
|
+
| `body?` | `string` | |
|
|
2762
|
+
| `draft?` | `boolean` | |
|
|
2763
|
+
| `prerelease?` | `boolean` | |
|
|
2764
|
+
| `target_commitish?` | `string` | |
|
|
2765
|
+
| `[key: string]` | `unknown` | |
|
|
2766
|
+
|
|
2767
|
+
---
|
|
2768
|
+
|
|
2172
2769
|
## SDK reference: idempotent
|
|
2173
2770
|
|
|
2174
|
-
Source: https://kici.dev/
|
|
2771
|
+
Source: https://docs.kici.dev/user/sdk/idempotent/
|
|
2175
2772
|
|
|
2176
2773
|
The SDK exposes two idempotency helpers — a generic function `idempotent()` and a step factory `idempotentStep()` — for the common case where a workflow step should:
|
|
2177
2774
|
|
|
@@ -2321,7 +2918,7 @@ The caller never has to branch on outcome — `result.result` is always a `Bucke
|
|
|
2321
2918
|
|
|
2322
2919
|
## SDK reference: rules, matrix, dynamic jobs
|
|
2323
2920
|
|
|
2324
|
-
Source: https://kici.dev/
|
|
2921
|
+
Source: https://docs.kici.dev/user/sdk/rules-matrix-dynamic/
|
|
2325
2922
|
|
|
2326
2923
|
## Rules
|
|
2327
2924
|
|
|
@@ -2442,17 +3039,7 @@ You can also narrow directly with `if (ctx.event.type === 'pull_request')` — T
|
|
|
2442
3039
|
|
|
2443
3040
|
`EventPayload` is a discriminated union over the `type` field. Each variant provides typed access to the normalized event fields and the raw webhook payload.
|
|
2444
3041
|
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
| Field | Type | Description |
|
|
2448
|
-
| ---------------- | ------------------------------ | --------------------------------------- |
|
|
2449
|
-
| `type` | `string` (literal per variant) | Event type discriminant |
|
|
2450
|
-
| `action` | `string \| undefined` | Sub-action (e.g. 'opened', 'created') |
|
|
2451
|
-
| `targetBranch` | `string \| undefined` | Target branch (push target, PR base) |
|
|
2452
|
-
| `sourceBranch` | `string \| undefined` | Source branch (PR head) |
|
|
2453
|
-
| `provider` | `string \| undefined` | Provider name (e.g. 'github') |
|
|
2454
|
-
| `senderUsername` | `string \| undefined` | Webhook sender username |
|
|
2455
|
-
| `payload` | `Record<string, unknown>` | Raw webhook payload (typed per variant) |
|
|
3042
|
+
Every variant carries the shared `EventBase` fields — `type`, `action`, `targetBranch`, `sourceBranch`, `provider`, `isForkPR`, `baseBranch`, `senderUsername`, `sourceRepo`, `changedFiles`, and the raw `payload` — plus a per-type `payload` shape for the typed variants. The complete field-by-field schema, including every typed `payload` shape and the shared GitHub object types, is in the [event payload reference](./event-payloads.md).
|
|
2456
3043
|
|
|
2457
3044
|
**Typed variants** (with GitHub-specific payload fields): `pull_request`, `push`, `tag`, `comment`, `review`, `review_comment`, `release`, `dispatch`, `create`, `delete`, `status`, `workflow_run`, `fork`, `star`, `watch`.
|
|
2458
3045
|
|
|
@@ -2480,16 +3067,16 @@ step('test', async ({ $, matrix }) => {
|
|
|
2480
3067
|
|
|
2481
3068
|
```typescript
|
|
2482
3069
|
matrix: {
|
|
2483
|
-
os: ['
|
|
3070
|
+
os: ['linux', 'arm64'],
|
|
2484
3071
|
node: ['18', '20'],
|
|
2485
3072
|
}
|
|
2486
3073
|
```
|
|
2487
3074
|
|
|
2488
|
-
Creates 4 job instances (2 x 2). In steps, values are named properties:
|
|
3075
|
+
Creates 4 job instances (2 x 2). The `os` values (`linux`, `arm64`) are **customer-defined scaler labels** matched by subset semantics against the labels your orchestrator advertises in its scaler `labelSets` — not hosted-runner names. In steps, values are named properties:
|
|
2489
3076
|
|
|
2490
3077
|
```typescript
|
|
2491
3078
|
step('test', async ({ $, matrix }) => {
|
|
2492
|
-
console.log(matrix!.os); // '
|
|
3079
|
+
console.log(matrix!.os); // 'linux' or 'arm64'
|
|
2493
3080
|
console.log(matrix!.node); // '18' or '20'
|
|
2494
3081
|
});
|
|
2495
3082
|
```
|
|
@@ -2522,14 +3109,14 @@ Fine-tune matrix combinations on multi-dimensional matrices:
|
|
|
2522
3109
|
|
|
2523
3110
|
```typescript
|
|
2524
3111
|
matrix: {
|
|
2525
|
-
os: ['
|
|
3112
|
+
os: ['linux', 'arm64', 'windows'],
|
|
2526
3113
|
node: ['18', '20', '22'],
|
|
2527
3114
|
},
|
|
2528
3115
|
exclude: [
|
|
2529
|
-
{ os: 'windows
|
|
3116
|
+
{ os: 'windows', node: '18' },
|
|
2530
3117
|
],
|
|
2531
3118
|
include: [
|
|
2532
|
-
{ os: '
|
|
3119
|
+
{ os: 'linux', node: '23' },
|
|
2533
3120
|
],
|
|
2534
3121
|
```
|
|
2535
3122
|
|
|
@@ -2599,7 +3186,7 @@ const discoverJobs: DynamicJobFn = async ({ $ }) => {
|
|
|
2599
3186
|
const packages = result.stdout.trim().split('\n');
|
|
2600
3187
|
return packages.map((pkg) =>
|
|
2601
3188
|
job(`test-${pkg}`, {
|
|
2602
|
-
runsOn: '
|
|
3189
|
+
runsOn: 'linux',
|
|
2603
3190
|
steps: [
|
|
2604
3191
|
step('test', async ({ $ }) => {
|
|
2605
3192
|
await $`cd packages/${pkg} && pnpm test`;
|
|
@@ -2644,7 +3231,7 @@ for (const item of workflow.jobs) {
|
|
|
2644
3231
|
|
|
2645
3232
|
## SDK reference: runtime
|
|
2646
3233
|
|
|
2647
|
-
Source: https://kici.dev/
|
|
3234
|
+
Source: https://docs.kici.dev/user/sdk/runtime/
|
|
2648
3235
|
|
|
2649
3236
|
## Types
|
|
2650
3237
|
|
|
@@ -2858,7 +3445,7 @@ Use whichever style is convenient — you don't have to wrap `console.log` in th
|
|
|
2858
3445
|
|
|
2859
3446
|
### setEnv(key, value)
|
|
2860
3447
|
|
|
2861
|
-
|
|
3448
|
+
Export an environment variable to later steps in the same job. This is the canonical way to hand a value computed in one step to the steps that follow — the equivalent of `echo "KEY=VALUE" >> $GITHUB_ENV` in GitHub Actions. The value is visible to the current step and all subsequent steps in the job.
|
|
2862
3449
|
|
|
2863
3450
|
```typescript
|
|
2864
3451
|
step('setup', async (ctx) => {
|
|
@@ -2879,6 +3466,7 @@ step('use', async (ctx) => {
|
|
|
2879
3466
|
- Last-write-wins -- if multiple steps set the same key, the last value is used
|
|
2880
3467
|
- Cannot override operator-injected secrets (the operator value takes precedence)
|
|
2881
3468
|
- Changes take effect immediately in the current step and persist for all subsequent steps
|
|
3469
|
+
- Shell commands export the same way by appending to `$KICI_ENV` (see [Exporting env from shell commands](#exporting-env-from-shell-commands-kici_env--kici_path) below)
|
|
2882
3470
|
|
|
2883
3471
|
### addPath(dir)
|
|
2884
3472
|
|
|
@@ -2896,6 +3484,40 @@ step('build', async (ctx) => {
|
|
|
2896
3484
|
});
|
|
2897
3485
|
```
|
|
2898
3486
|
|
|
3487
|
+
### Exporting env from shell commands ($KICI_ENV / $KICI_PATH)
|
|
3488
|
+
|
|
3489
|
+
`setEnv` and `addPath` are the TypeScript form of "export env to later steps". A shell command — including a non-JS toolchain installer — exports env the same way by appending to two files the agent points at before every step:
|
|
3490
|
+
|
|
3491
|
+
- **`$KICI_ENV`** — append `KEY=value` lines. Each becomes an environment variable visible to subsequent steps, exactly like `ctx.setEnv('KEY', 'value')`.
|
|
3492
|
+
- **`$KICI_PATH`** — append one directory per line. Each is prepended to `PATH` for subsequent steps, exactly like `ctx.addPath(dir)`. The first directory appended ends up first on `PATH`.
|
|
3493
|
+
|
|
3494
|
+
```typescript
|
|
3495
|
+
step('install-tool', async (ctx) => {
|
|
3496
|
+
await ctx.$`./install-mytool.sh`; // installs to /opt/mytool
|
|
3497
|
+
// Export from the shell, no JS round-trip needed:
|
|
3498
|
+
await ctx.$`echo "MYTOOL_HOME=/opt/mytool" >> "$KICI_ENV"`;
|
|
3499
|
+
await ctx.$`echo "/opt/mytool/bin" >> "$KICI_PATH"`;
|
|
3500
|
+
});
|
|
3501
|
+
|
|
3502
|
+
step('build', async (ctx) => {
|
|
3503
|
+
// MYTOOL_HOME is set and /opt/mytool/bin is on PATH here.
|
|
3504
|
+
await ctx.$`mytool build`;
|
|
3505
|
+
});
|
|
3506
|
+
```
|
|
3507
|
+
|
|
3508
|
+
**Format (v1):**
|
|
3509
|
+
|
|
3510
|
+
- One `KEY=value` per line in `$KICI_ENV`. The split is on the first `=`, so the value may contain `=`. Blank lines and lines without a `=` are ignored.
|
|
3511
|
+
- One directory per line in `$KICI_PATH`. Blank lines are ignored.
|
|
3512
|
+
- Values must be single-line — embedded newlines are not supported in v1.
|
|
3513
|
+
|
|
3514
|
+
**Behavior (shared with `setEnv` / `addPath`):**
|
|
3515
|
+
|
|
3516
|
+
- Applied after the step completes and visible to every later step in the job.
|
|
3517
|
+
- Last-write-wins on a repeated key.
|
|
3518
|
+
- Cannot override an operator-injected secret — a collision is ignored and logged, and the operator value is preserved.
|
|
3519
|
+
- The files are reset before each step, so each step sees only its own appended lines.
|
|
3520
|
+
|
|
2899
3521
|
### setSecretOutput(key, value)
|
|
2900
3522
|
|
|
2901
3523
|
Publish an encrypted secret output from this job. Downstream jobs that list this job in their `needs` array receive the value merged into `ctx.secrets`.
|
|
@@ -2944,7 +3566,7 @@ Each job picks its secret environment via the `environment` option on `job()`. T
|
|
|
2944
3566
|
|
|
2945
3567
|
```typescript
|
|
2946
3568
|
const deploy = job('deploy', {
|
|
2947
|
-
runsOn: '
|
|
3569
|
+
runsOn: 'linux',
|
|
2948
3570
|
environment: 'production',
|
|
2949
3571
|
steps: [
|
|
2950
3572
|
/* ... */
|
|
@@ -2983,7 +3605,7 @@ step('deploy', async ({ secrets }) => {
|
|
|
2983
3605
|
import { workflow, job, step, push } from '@kici-dev/sdk';
|
|
2984
3606
|
|
|
2985
3607
|
const deploy = job('deploy', {
|
|
2986
|
-
runsOn: '
|
|
3608
|
+
runsOn: 'linux',
|
|
2987
3609
|
environment: 'production',
|
|
2988
3610
|
steps: [
|
|
2989
3611
|
step('deploy', async (ctx) => {
|
|
@@ -3110,7 +3732,7 @@ Options can also be provided as an async factory function for dynamic fixture ge
|
|
|
3110
3732
|
|
|
3111
3733
|
## SDK reference: triggers
|
|
3112
3734
|
|
|
3113
|
-
Source: https://kici.dev/
|
|
3735
|
+
Source: https://docs.kici.dev/user/sdk/triggers/
|
|
3114
3736
|
|
|
3115
3737
|
## Triggers
|
|
3116
3738
|
|
|
@@ -3611,7 +4233,7 @@ Glob patterns use micromatch syntax. Regex patterns use standard JavaScript `Reg
|
|
|
3611
4233
|
|
|
3612
4234
|
## SDK reference: validation & events
|
|
3613
4235
|
|
|
3614
|
-
Source: https://kici.dev/
|
|
4236
|
+
Source: https://docs.kici.dev/user/sdk/validation-events/
|
|
3615
4237
|
|
|
3616
4238
|
## Validation
|
|
3617
4239
|
|
|
@@ -3774,7 +4396,7 @@ The orchestrator automatically emits system events for workflow and job completi
|
|
|
3774
4396
|
|
|
3775
4397
|
## SDK reference: waitFor
|
|
3776
4398
|
|
|
3777
|
-
Source: https://kici.dev/
|
|
4399
|
+
Source: https://docs.kici.dev/user/sdk/wait-for/
|
|
3778
4400
|
|
|
3779
4401
|
The SDK exposes two wait-for helpers — a generic function `waitFor()` and a step factory `waitForStep()` — for the common case where a workflow step should:
|
|
3780
4402
|
|
|
@@ -3890,7 +4512,7 @@ If `check()` throws while polling, the error is logged and polling continues —
|
|
|
3890
4512
|
|
|
3891
4513
|
## SDK reference
|
|
3892
4514
|
|
|
3893
|
-
Source: https://kici.dev/
|
|
4515
|
+
Source: https://docs.kici.dev/user/sdk-reference/
|
|
3894
4516
|
|
|
3895
4517
|
Reference documentation for `@kici-dev/sdk`. The reference is split across five pages by topic.
|
|
3896
4518
|
|
|
@@ -3899,6 +4521,7 @@ Reference documentation for `@kici-dev/sdk`. The reference is split across five
|
|
|
3899
4521
|
| [Core](./sdk/core.md) | `workflow()`, `job()`, `step()` factory functions and step / job authoring patterns (bare functions, output chaining, `needs`, dynamic groups). |
|
|
3900
4522
|
| [Triggers](./sdk/triggers.md) | All 22 trigger factories -- GitHub events (`pr`, `push`, `tag`, `comment`, ...), event triggers (`kiciEvent`, `workflowComplete`, `jobComplete`), `genericWebhook`, `schedule`, `lifecycle`, plus branch-pattern semantics. |
|
|
3901
4523
|
| [Rules, matrix, dynamic jobs](./sdk/rules-matrix-dynamic.md) | `rule()`, `skip()`, matrix builds (static + dynamic), and `dynamicJob()` / `dynamicGroup()`. |
|
|
4524
|
+
| [Caching](./sdk/caching.md) | `CacheSpec`, declarative `cache` on jobs/steps, imperative `ctx.cache.restore()` / `ctx.cache.save()`, immutable keys, `restoreKeys` prefix fallback, per-org + per-ref isolation. |
|
|
3902
4525
|
| [Validation & events](./sdk/validation-events.md) | `validateDag()`, `defineEvent()`, event emission patterns. |
|
|
3903
4526
|
| [Runtime](./sdk/runtime.md) | Types index, `StepContext`, secrets, and fixtures. |
|
|
3904
4527
|
| [Idempotent helpers](./sdk/idempotent.md) | `idempotent()` and `idempotentStep()` — check / apply pattern with typed results on both the skipped and applied branches. |
|
|
@@ -3927,7 +4550,7 @@ For the complete list of every named export (factory functions, triggers, rules,
|
|
|
3927
4550
|
|
|
3928
4551
|
## CLI authentication
|
|
3929
4552
|
|
|
3930
|
-
Source: https://kici.dev/
|
|
4553
|
+
Source: https://docs.kici.dev/user/cli-auth/
|
|
3931
4554
|
|
|
3932
4555
|
The KiCI CLI supports three authentication methods: browser-based OAuth (default), device authorization flow (for headless environments), and API key paste (for CI/CD pipelines).
|
|
3933
4556
|
|
|
@@ -4089,7 +4712,7 @@ JWT and opaque OIDC tokens are validated against the configured OIDC issuer (JWK
|
|
|
4089
4712
|
|
|
4090
4713
|
### Permissions
|
|
4091
4714
|
|
|
4092
|
-
Tokens authenticate; RBAC authorizes. Every org-scoped route runs `orgContextMiddleware` (verifies you are a member of the target org) followed by `requirePermission(resource, level)`. The
|
|
4715
|
+
Tokens authenticate; RBAC authorizes. Every org-scoped route runs `orgContextMiddleware` (verifies you are a member of the target org) followed by `requirePermission(resource, level)`. The 15 resources and 5 levels are documented in [RBAC](../architecture/security/rbac.md#permission-model). User API keys carry their own permission matrix bounded above by the creator's effective permissions; PATs inherit the user's role permissions (or are capped further by their `scopes` field).
|
|
4093
4716
|
|
|
4094
4717
|
### Configurable surfaces
|
|
4095
4718
|
|
|
@@ -4188,7 +4811,7 @@ If the CLI can't reach the server:
|
|
|
4188
4811
|
|
|
4189
4812
|
## CLI reference
|
|
4190
4813
|
|
|
4191
|
-
Source: https://kici.dev/
|
|
4814
|
+
Source: https://docs.kici.dev/user/cli-reference/
|
|
4192
4815
|
|
|
4193
4816
|
The `@kici-dev/compiler` package provides the `kici` CLI for compiling, testing, and managing workflows.
|
|
4194
4817
|
|
|
@@ -4346,6 +4969,7 @@ Cleanup policy:
|
|
|
4346
4969
|
- On a fully successful run, the isolated checkout is removed.
|
|
4347
4970
|
- On failure, it is retained and its path is logged so you can inspect the failed state.
|
|
4348
4971
|
- `--keep` always retains it, even on success.
|
|
4972
|
+
- Retained checkouts are garbage-collected after 72 hours by the next `kici run local` invocation — copy a checkout elsewhere if you need it longer.
|
|
4349
4973
|
|
|
4350
4974
|
Set the `KICI_RUN_DIR` environment variable to place the isolated checkout under a base directory other than the system temp directory.
|
|
4351
4975
|
|
|
@@ -4402,6 +5026,8 @@ kici run local push --keep-going
|
|
|
4402
5026
|
|
|
4403
5027
|
Execute fixtures remotely through the full CI pipeline. Fixtures are defined in `.kici/tests/*.ts` using the `fixture()` factory function. Without arguments, lists available fixtures.
|
|
4404
5028
|
|
|
5029
|
+
Requires `kici login` (an authenticated session) and a target orchestrator that has **cache storage configured** (`KICI_STORAGE_TYPE` = `s3` or `filesystem`) — the command uploads your working-tree overlay to that storage for the agent to fetch. The quickstart orchestrators do not enable storage by default; see the [testing guide](testing-guide.md) for setup (including non-public / self-hosted S3 endpoints).
|
|
5030
|
+
|
|
4405
5031
|
```bash
|
|
4406
5032
|
kici run remote [fixture] [options]
|
|
4407
5033
|
```
|
|
@@ -4414,21 +5040,21 @@ kici run remote [fixture] [options]
|
|
|
4414
5040
|
|
|
4415
5041
|
**Options:**
|
|
4416
5042
|
|
|
4417
|
-
| Option | Default | Description
|
|
4418
|
-
| --------------------------- | ------- |
|
|
4419
|
-
| `--all` | `false` | Run all fixtures
|
|
4420
|
-
| `--workflow <name>` | none | Run a specific workflow directly (bypass triggers)
|
|
4421
|
-
| `--parallel` | `false` | Run multiple fixtures concurrently
|
|
4422
|
-
| `--no-wait` | - | Fire and forget (print runIds, don't stream)
|
|
4423
|
-
| `--quiet` | `false` | Minimal output (only final result)
|
|
4424
|
-
| `--json` | `false` | Machine-readable JSON output
|
|
4425
|
-
| `--junit <path>` | none | JUnit XML output to file for CI integration
|
|
4426
|
-
| `--history` | `false` | Show table of recent test runs
|
|
4427
|
-
| `--routing-key <key>` | none | Override routing key for this run
|
|
4428
|
-
| `--
|
|
4429
|
-
| `--
|
|
4430
|
-
| `--debug` | `false` | Verbose internals
|
|
4431
|
-
| `--kici-dir <path>` | `.kici` | Path to .kici directory
|
|
5043
|
+
| Option | Default | Description |
|
|
5044
|
+
| --------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
5045
|
+
| `--all` | `false` | Run all fixtures |
|
|
5046
|
+
| `--workflow <name>` | none | Run a specific workflow directly (bypass triggers) |
|
|
5047
|
+
| `--parallel` | `false` | Run multiple fixtures concurrently |
|
|
5048
|
+
| `--no-wait` | - | Fire and forget (print runIds, don't stream) |
|
|
5049
|
+
| `--quiet` | `false` | Minimal output (only final result) |
|
|
5050
|
+
| `--json` | `false` | Machine-readable JSON output |
|
|
5051
|
+
| `--junit <path>` | none | JUnit XML output to file for CI integration |
|
|
5052
|
+
| `--history` | `false` | Show table of recent test runs |
|
|
5053
|
+
| `--routing-key <key>` | none | Override routing key for this run |
|
|
5054
|
+
| `--context <ctx.key=value>` | none | Inject a namespaced context secret, uploaded encrypted to the orchestrator (repeatable) |
|
|
5055
|
+
| `--env <KEY=VALUE>` | none | Provide a per-run secret, uploaded encrypted to the orchestrator (repeatable) — see [testing guide](testing-guide.md) |
|
|
5056
|
+
| `--debug` | `false` | Verbose internals |
|
|
5057
|
+
| `--kici-dir <path>` | `.kici` | Path to .kici directory |
|
|
4432
5058
|
|
|
4433
5059
|
**Examples:**
|
|
4434
5060
|
|
|
@@ -4561,15 +5187,14 @@ kici login [options]
|
|
|
4561
5187
|
|
|
4562
5188
|
**Environment variables:**
|
|
4563
5189
|
|
|
4564
|
-
| Variable
|
|
4565
|
-
|
|
|
4566
|
-
| `KICI_PLATFORM_URL`
|
|
4567
|
-
| `KICI_OIDC_ISSUER`
|
|
4568
|
-
| `KICI_OIDC_CLIENT_ID`
|
|
4569
|
-
| `
|
|
4570
|
-
| `
|
|
4571
|
-
| `
|
|
4572
|
-
| `KICI_CONFIG_DIR` | `~/.kici` | Override config directory |
|
|
5190
|
+
| Variable | Default | Description |
|
|
5191
|
+
| --------------------- | -------------------------------------------- | ---------------------------------------------------------------------- |
|
|
5192
|
+
| `KICI_PLATFORM_URL` | `https://api.kici.dev` | Platform API base URL (override for a self-hosted Platform) |
|
|
5193
|
+
| `KICI_OIDC_ISSUER` | `https://auth.kici.dev/realms/kici-internal` | OIDC issuer URL (override for a self-hosted Platform) |
|
|
5194
|
+
| `KICI_OIDC_CLIENT_ID` | `kici-cli` | OIDC client ID (override for a self-hosted Platform) |
|
|
5195
|
+
| `KICI_BROWSER_CMD` | uses `open` package | Custom browser command with `{url}` placeholder, or `none` to suppress |
|
|
5196
|
+
| `KICI_CALLBACK_PORT` | random | Fixed port for OAuth PKCE callback server |
|
|
5197
|
+
| `KICI_CONFIG_DIR` | `~/.kici` | Override config directory |
|
|
4573
5198
|
|
|
4574
5199
|
**Examples:**
|
|
4575
5200
|
|
|
@@ -5162,7 +5787,7 @@ Set `KICI_DEBUG=true` for additional internal debug output across all commands.
|
|
|
5162
5787
|
|
|
5163
5788
|
## Lifecycle hooks
|
|
5164
5789
|
|
|
5165
|
-
Source: https://kici.dev/
|
|
5790
|
+
Source: https://docs.kici.dev/user/hooks/
|
|
5166
5791
|
|
|
5167
5792
|
Hooks are callbacks that run at specific points in the execution lifecycle. They let you react to outcomes (cancellation, success, failure) and perform cleanup without affecting the execution flow.
|
|
5168
5793
|
|
|
@@ -5402,7 +6027,7 @@ _Source: `packages/sdk/src/hooks/`, `packages/sdk/src/types.ts`_
|
|
|
5402
6027
|
|
|
5403
6028
|
## Lock file and workflow drift
|
|
5404
6029
|
|
|
5405
|
-
Source: https://kici.dev/
|
|
6030
|
+
Source: https://docs.kici.dev/user/lock-file-and-drift/
|
|
5406
6031
|
|
|
5407
6032
|
KiCI uses a **two-artifact model**: TypeScript workflows are the source of truth; the lock file (`kici.lock.json`) is the execution contract. The orchestrator reads only the lock file to match triggers and decide cache vs build. Keeping these in sync is important.
|
|
5408
6033
|
|
|
@@ -5417,13 +6042,13 @@ If you change a workflow file (`.ts`) but do **not** regenerate and commit the l
|
|
|
5417
6042
|
|
|
5418
6043
|
The lock file (`kici.lock.json`) is a JSON file with the following top-level fields:
|
|
5419
6044
|
|
|
5420
|
-
| Field | Description
|
|
5421
|
-
| --------------- |
|
|
5422
|
-
| `schemaVersion` | Lock file schema version (currently
|
|
5423
|
-
| `source` | Reference to the source file and export (e.g., `{ file: “.kici/workflows/ci.ts”, export: “#default” }`).
|
|
5424
|
-
| `contentHash` | SHA-256 of the serialized lock file content (excluding itself). Changes when any workflow, trigger, or job changes.
|
|
5425
|
-
| `lockfileHash` | SHA-256 of `.kici/package-lock.json
|
|
5426
|
-
| `workflows` | Array of workflow entries, each with its own `contentHash`, `compileSchemaVersion`, triggers, and jobs.
|
|
6045
|
+
| Field | Description |
|
|
6046
|
+
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
6047
|
+
| `schemaVersion` | Lock file schema version (currently 15). Incremented on breaking format changes. |
|
|
6048
|
+
| `source` | Reference to the source file and export (e.g., `{ file: “.kici/workflows/ci.ts”, export: “#default” }`). |
|
|
6049
|
+
| `contentHash` | SHA-256 of the serialized lock file content (excluding itself). Changes when any workflow, trigger, or job changes. |
|
|
6050
|
+
| `lockfileHash` | SHA-256 of the detected package manager's lockfile, used as the dependency cache key. The lockfile is `.kici/package-lock.json` for npm, or the repo-root `pnpm-lock.yaml` / `yarn.lock` for a pnpm/yarn workspace; the hash input is prefixed with the manager name so a manager change is a guaranteed cache miss. Omitted when no lockfile exists. |
|
|
6051
|
+
| `workflows` | Array of workflow entries, each with its own `contentHash`, `compileSchemaVersion`, triggers, and jobs. |
|
|
5427
6052
|
|
|
5428
6053
|
Each workflow entry includes:
|
|
5429
6054
|
|
|
@@ -5439,7 +6064,11 @@ Each workflow entry includes:
|
|
|
5439
6064
|
| `description` | Optional workflow description. |
|
|
5440
6065
|
| `hashFiles` | Declared glob patterns for extra files included in the content hash (optional). See [extra files in the content hash](#extra-files-in-the-content-hash-hashfiles). |
|
|
5441
6066
|
| `resolvedHashFiles` | Resolved file paths from `hashFiles` at compile time (optional). Recorded so the agent can verify without re-discovering. |
|
|
6067
|
+
| `contexts` | Secret contexts declared by the workflow (optional). The orchestrator validates access to each context before dispatch. |
|
|
6068
|
+
| `registries` | Private npm registry declarations the agent authenticates against before install (optional): `url`, `scope`, `tokenSecret` reference, `alwaysAuth`. Resolved token bytes never appear in the lock file. See [private registries](private-registries.md). |
|
|
6069
|
+
| `installEnv` | Extra qualified secret refs (`<environment>:<secret-name>`) projected as env vars on the install subprocess for use with a committed `.kici/.npmrc` (optional). See [private registries](private-registries.md). |
|
|
5442
6070
|
| `concurrency` | Workflow-level concurrency config: `hasGroup`, `cancelInProgress`, `max` (optional). See [concurrency groups](concurrency.md). |
|
|
6071
|
+
| `timeout` | Whole-run wall-clock timeout in milliseconds (optional). The orchestrator reads this at run creation to set the run deadline. |
|
|
5443
6072
|
| Hook flags | Boolean flags (`hasOnCancel`, `hasCleanup`, `hasOnSuccess`, `hasOnFailure`) indicating which lifecycle hooks are defined. Job entries additionally have `hasBeforeStep` and `hasAfterStep`. |
|
|
5444
6073
|
|
|
5445
6074
|
## Rule: commit both together
|
|
@@ -5525,7 +6154,7 @@ So even without a pre-commit or CI check, a stale lock file will cause the run t
|
|
|
5525
6154
|
|
|
5526
6155
|
## Testing guide
|
|
5527
6156
|
|
|
5528
|
-
Source: https://kici.dev/
|
|
6157
|
+
Source: https://docs.kici.dev/user/testing-guide/
|
|
5529
6158
|
|
|
5530
6159
|
Test your workflows remotely against the full CI pipeline from your local machine. `kici run remote` uploads your current repo state (including uncommitted changes), triggers the pipeline, and streams execution logs back in real time.
|
|
5531
6160
|
|
|
@@ -5535,11 +6164,22 @@ Test your workflows remotely against the full CI pipeline from your local machin
|
|
|
5535
6164
|
|
|
5536
6165
|
- Run any workflow against your current working tree (including unstaged changes)
|
|
5537
6166
|
- Get real-time log output streamed back to your terminal
|
|
5538
|
-
-
|
|
6167
|
+
- Give test runs test-scoped secrets — your local secret files and `--env` values (uploaded encrypted) plus any environment flagged `allowLocalExecution: true` — while production environments stay unreachable
|
|
5539
6168
|
- Detect test mode in workflow code via `ctx.isTestRun`
|
|
5540
6169
|
|
|
5541
6170
|
The command is remote-only -- all execution happens on the orchestrator and agent. For local-only trigger matching previews, use `kici test <event>`.
|
|
5542
6171
|
|
|
6172
|
+
:::note[Orchestrator prerequisite: cache storage]
|
|
6173
|
+
`kici run remote` uploads your working-tree overlay to the orchestrator's **cache storage** via a pre-signed URL, and the agent fetches it from there (see [Repo state transfer](#repo-state-transfer)). The target orchestrator must therefore have cache storage enabled (`KICI_STORAGE_TYPE` = `s3` or `filesystem`).
|
|
6174
|
+
|
|
6175
|
+
- **The [Docker / Podman quickstart](quickstart/compose.md) wires this up for you** — it ships a SeaweedFS service, so `kici run remote` works there out of the box (see its "run a workflow without pushing" step).
|
|
6176
|
+
- **The [bare-metal quickstart](quickstart/bare-metal.md) does not configure storage by default** — enable a backend before using `kici run remote`:
|
|
6177
|
+
- **`filesystem`** — simplest for a single-host orchestrator: set `KICI_STORAGE_TYPE=filesystem` and `KICI_STORAGE_FS_PATH=/var/lib/kici/cache`. No external service needed; blobs are served through the orchestrator's own HMAC-signed HTTP route.
|
|
6178
|
+
- **`s3`** — any S3-compatible bucket. **A non-public / self-hosted endpoint works**: set `KICI_STORAGE_TYPE=s3`, `KICI_STORAGE_BUCKET`, `KICI_STORAGE_ENDPOINT=https://your-endpoint` and (for most self-hosted services) `KICI_STORAGE_FORCE_PATH_STYLE=true`. If the developer machine running `kici run remote` reaches the bucket at a different address than the orchestrator, set `KICI_STORAGE_UPLOAD_ENDPOINT` to the developer-reachable address; if agents reach it at yet another address (e.g. agents in containers), set `KICI_STORAGE_EXTERNAL_ENDPOINT` to the agent-routable URL.
|
|
6179
|
+
|
|
6180
|
+
See [Storage layout](../operator/orchestrator/storage-layout.md) for the full env-var reference.
|
|
6181
|
+
:::
|
|
6182
|
+
|
|
5543
6183
|
## Getting started
|
|
5544
6184
|
|
|
5545
6185
|
### 1. Authenticate
|
|
@@ -5638,7 +6278,7 @@ When not specified, these default to values detected from your local git repo (c
|
|
|
5638
6278
|
|
|
5639
6279
|
### Secret context mappings
|
|
5640
6280
|
|
|
5641
|
-
Map
|
|
6281
|
+
Map secret contexts to your fixture:
|
|
5642
6282
|
|
|
5643
6283
|
```typescript
|
|
5644
6284
|
export const pushWithSecrets = fixture('push-with-secrets', {
|
|
@@ -5650,7 +6290,12 @@ export const pushWithSecrets = fixture('push-with-secrets', {
|
|
|
5650
6290
|
});
|
|
5651
6291
|
```
|
|
5652
6292
|
|
|
5653
|
-
This maps the `db` secret context to the `test-database` context
|
|
6293
|
+
This maps the `db` secret context to the `test-database` context, and `api` to `test-api-keys`.
|
|
6294
|
+
|
|
6295
|
+
This mapping is honored by **both** `kici run local` and `kici run remote`:
|
|
6296
|
+
|
|
6297
|
+
- For **`kici run local`** (see [`kici run local`](cli-reference.md#kici-run-local)), each named context is resolved from your local secret files (`.kici/.secrets`, `.env.local`, `secrets.yaml`, and `--env` flags).
|
|
6298
|
+
- For **`kici run remote`**, each named context maps to an orchestrator **environment**, and the orchestrator resolves that environment's secrets for the run. The target environment must be flagged `allowLocalExecution: true` — mapping a context to a missing or non-test environment rejects the run (see [Secret contexts for testing](#secret-contexts-for-testing) below).
|
|
5654
6299
|
|
|
5655
6300
|
### Async fixtures
|
|
5656
6301
|
|
|
@@ -5781,39 +6426,76 @@ The CLI always shows a pre-upload summary before transferring:
|
|
|
5781
6426
|
|
|
5782
6427
|
## Secret contexts for testing
|
|
5783
6428
|
|
|
5784
|
-
|
|
6429
|
+
The goal of the test-secret model is to let test runs reach **test-only credentials** while keeping production credentials out of reach. `kici run remote` combines two sources of secrets for a test run, then merges them with a clear precedence and a fail-closed gate.
|
|
6430
|
+
|
|
6431
|
+
### CLI-uploaded local secrets
|
|
6432
|
+
|
|
6433
|
+
`kici run remote` collects the same local secret values that `kici run local` reads — `.kici/.secrets`, `.kici/.env.local`, `.kici/secrets.yaml`, and any `--env KEY=VALUE` flags — and uploads them **encrypted** to the orchestrator alongside the run. The orchestrator decrypts them only to inject them into the agent for that run; the control plane never sees the values.
|
|
5785
6434
|
|
|
5786
|
-
|
|
6435
|
+
```bash
|
|
6436
|
+
# Provide an ad-hoc test value for a single remote run
|
|
6437
|
+
kici run remote push-main --env KICI_DATABASE_URL=postgresql://localhost/test
|
|
6438
|
+
```
|
|
6439
|
+
|
|
6440
|
+
`--env` provides a **flat** per-run override; `--context <ctx>.<KEY>=<value>` is its sibling for a **namespaced** per-run override, placing the value under the named context `ctx`. Both are uploaded **encrypted** and follow the same precedence rule below — a CLI-supplied value wins over the orchestrator test-environment secret on a key collision.
|
|
6441
|
+
|
|
6442
|
+
```bash
|
|
6443
|
+
# Provide a namespaced per-run value under the 'db' context
|
|
6444
|
+
kici run remote push-db --context db.KICI_DATABASE_URL=postgresql://localhost/test
|
|
6445
|
+
```
|
|
5787
6446
|
|
|
5788
|
-
|
|
6447
|
+
Because these values originate on your machine, they are the natural place to put throwaway test credentials without touching any orchestrator-stored secret.
|
|
5789
6448
|
|
|
5790
|
-
|
|
6449
|
+
### Orchestrator test-environment secrets
|
|
5791
6450
|
|
|
5792
|
-
|
|
6451
|
+
In addition to your uploaded values, the orchestrator resolves test-scoped secrets from its own store for a remote test run:
|
|
5793
6452
|
|
|
5794
|
-
|
|
6453
|
+
- The job's own declared `environment` contributes its resolved secrets (flat). Static strings and **pure dynamic functions** both participate: a pure `environment:` function (see [Dynamic values](dynamic-values.md)) is evaluated against the fixture's simulated event, and the resolved name is gated and resolved like a static one. Impure dynamic functions (those requiring an init job) are not evaluated for test runs — use a fixture `secrets:` mapping (or `--context`) to supply such a job's secrets.
|
|
6454
|
+
- Each fixture `secrets: { ctx: envName }` mapping resolves the named environment's secrets under the namespaced context `ctx`.
|
|
6455
|
+
|
|
6456
|
+
Both paths are restricted to environments flagged `allowLocalExecution: true`. A production environment left at the default `false` is never resolvable for a test run.
|
|
5795
6457
|
|
|
5796
6458
|
```typescript
|
|
5797
6459
|
export const pushWithDb = fixture('push-db', {
|
|
5798
6460
|
event: push({ branches: ['main'] }),
|
|
5799
|
-
secrets: { db: 'test-database' },
|
|
6461
|
+
secrets: { db: 'test-database' }, // 'test-database' must be allowLocalExecution: true
|
|
5800
6462
|
});
|
|
5801
6463
|
```
|
|
5802
6464
|
|
|
5803
|
-
In your workflow, access secrets normally via `ctx.secrets` or `ctx.contexts`:
|
|
5804
|
-
|
|
5805
6465
|
```typescript
|
|
5806
6466
|
step('migrate', async (ctx) => {
|
|
5807
|
-
const dbUrl = ctx.secrets.KICI_DATABASE_URL;
|
|
6467
|
+
const dbUrl = await ctx.secrets.get('KICI_DATABASE_URL');
|
|
5808
6468
|
await ctx.$`npx prisma migrate deploy`;
|
|
5809
6469
|
});
|
|
5810
6470
|
```
|
|
5811
6471
|
|
|
5812
|
-
|
|
6472
|
+
### Precedence: CLI values win
|
|
6473
|
+
|
|
6474
|
+
When a key exists in both sources, the **CLI-uploaded local value wins** over the orchestrator test-environment value. This makes a local override a per-run knob: set `--env KICI_DATABASE_URL=...` (or put it in `.kici/.secrets`) to shadow the test environment's value for just that run, without changing anything on the orchestrator.
|
|
6475
|
+
|
|
6476
|
+
### Fail-closed on non-test environments
|
|
6477
|
+
|
|
6478
|
+
Test-run secret resolution is fail-closed:
|
|
6479
|
+
|
|
6480
|
+
- If a fixture maps a context to an environment that does not exist, the run is **rejected**.
|
|
6481
|
+
- If a fixture maps a context to an environment whose `allowLocalExecution` is `false`, the run is **rejected**.
|
|
6482
|
+
- The `allowLocalExecution` gate applies to **all** remote test runs: a run whose matched workflow targets an environment with the flag off is rejected, so a test run can never resolve production secrets.
|
|
6483
|
+
|
|
6484
|
+
### The `allowLocalExecution` environment flag
|
|
6485
|
+
|
|
6486
|
+
Each environment carries an `allowLocalExecution` flag (default `false`) that controls test-run access to that environment and to its secrets. Production environments should leave it at `false`; create a dedicated test environment with `allowLocalExecution: true` that binds only test-only secret scopes for the contexts you want test runs to use.
|
|
6487
|
+
|
|
6488
|
+
The flag is set by the orchestrator operator, either via the CLI:
|
|
6489
|
+
|
|
6490
|
+
```bash
|
|
6491
|
+
kici-admin environment set-policy --env test-database --allow-local-execution true
|
|
6492
|
+
```
|
|
6493
|
+
|
|
6494
|
+
or via the dashboard's "Test runs" toggle on the environment detail page. `kici secrets list` only surfaces contexts whose owning environment has `allowLocalExecution: true`, so production environments are never advertised as test-accessible.
|
|
5813
6495
|
|
|
5814
|
-
###
|
|
6496
|
+
### Local execution as an alternative
|
|
5815
6497
|
|
|
5816
|
-
|
|
6498
|
+
`kici run local` resolves the same local secret files entirely on your machine and honors the fixture `secrets: { ... }` mapping to pick which local context backs each name (see [`kici run local`](cli-reference.md#kici-run-local)). Because the values never leave your machine, it's a good fit when you want to exercise secret-dependent steps without involving the orchestrator at all.
|
|
5817
6499
|
|
|
5818
6500
|
### Discovering available contexts
|
|
5819
6501
|
|
|
@@ -5898,7 +6580,7 @@ export const pushMain = fixture('push-main', {
|
|
|
5898
6580
|
|
|
5899
6581
|
## Workflow patterns
|
|
5900
6582
|
|
|
5901
|
-
Source: https://kici.dev/
|
|
6583
|
+
Source: https://docs.kici.dev/user/workflow-patterns/
|
|
5902
6584
|
|
|
5903
6585
|
Practical patterns for building real-world KiCI workflows in TypeScript. The patterns are organised across five pages -- start with [Basic CI](./patterns/basic.md) if you're new, or jump to [Integrations](./patterns/integrations.md) if you're wiring up a non-GitHub forge or a generic webhook.
|
|
5904
6586
|
|
|
@@ -5925,7 +6607,7 @@ Practical patterns for building real-world KiCI workflows in TypeScript. The pat
|
|
|
5925
6607
|
|
|
5926
6608
|
## Concurrency groups
|
|
5927
6609
|
|
|
5928
|
-
Source: https://kici.dev/
|
|
6610
|
+
Source: https://docs.kici.dev/user/concurrency/
|
|
5929
6611
|
|
|
5930
6612
|
Concurrency groups prevent multiple workflow runs from executing in parallel when they target the same resource. Common use cases include preventing parallel deploys to the same environment or serializing database migrations.
|
|
5931
6613
|
|
|
@@ -5975,8 +6657,8 @@ group: (ctx) => `deploy-${ctx.branch}`;
|
|
|
5975
6657
|
// Global concurrency (across all branches)
|
|
5976
6658
|
group: () => 'deploy';
|
|
5977
6659
|
|
|
5978
|
-
// Per-
|
|
5979
|
-
group: (ctx) => `deploy-${ctx.event.
|
|
6660
|
+
// Per-target-branch concurrency
|
|
6661
|
+
group: (ctx) => `deploy-${ctx.event.targetBranch ?? 'default'}`;
|
|
5980
6662
|
```
|
|
5981
6663
|
|
|
5982
6664
|
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](environments.md#concurrency-groups)), where the compiler performs purity analysis and can inline pure functions for orchestrator-side evaluation.
|
|
@@ -6150,10 +6832,34 @@ _Source: `packages/sdk/src/types.ts` (WorkflowOptions.concurrency, JobOptions.co
|
|
|
6150
6832
|
|
|
6151
6833
|
## Dashboard
|
|
6152
6834
|
|
|
6153
|
-
Source: https://kici.dev/
|
|
6835
|
+
Source: https://docs.kici.dev/user/dashboard/
|
|
6154
6836
|
|
|
6155
6837
|
The KiCI dashboard is a web-based interface for monitoring workflow runs, inspecting job and step details, and reading log output. It is a browser single-page application that authenticates via OIDC and communicates with the Platform tier through REST API endpoints.
|
|
6156
6838
|
|
|
6839
|
+
## Getting started
|
|
6840
|
+
|
|
6841
|
+
<!-- help:getting-started-overview#getting-started -->
|
|
6842
|
+
|
|
6843
|
+
The getting-started page is a six-step checklist that takes you from zero to your first workflow run.
|
|
6844
|
+
|
|
6845
|
+
- **Self-checked steps** -- install the CLI, scaffold a workflow, and run it locally. These run on your own machine, so you tick them off yourself; the dashboard remembers your choices in the browser.
|
|
6846
|
+
- **Auto-detected steps** -- connect an orchestrator, add a webhook source, and trigger your first run. These tick automatically as the dashboard observes the matching activity in your organization.
|
|
6847
|
+
|
|
6848
|
+
Each step links to the relevant settings page or documentation. A progress bar tracks overall completion, and the sidebar entry shows a `done/total` badge until you finish or dismiss reminders.
|
|
6849
|
+
|
|
6850
|
+
<!-- /help:getting-started-overview -->
|
|
6851
|
+
|
|
6852
|
+
When you first sign in to a brand-new organization with no orchestrator, no webhook source, and no runs, the dashboard opens this page automatically. Once your organization has any activity, the run list becomes your landing page instead. The **Getting started** sidebar entry stays available so you can return to the checklist at any time.
|
|
6853
|
+
|
|
6854
|
+
The six steps are:
|
|
6855
|
+
|
|
6856
|
+
1. **Install the kici CLI** -- `npm install -g kici`.
|
|
6857
|
+
2. **Create a workflow** -- `kici init` scaffolds a `.kici/` directory in your repository.
|
|
6858
|
+
3. **Run a workflow locally** -- `kici run local pr:open` executes a workflow on your machine with no orchestrator required.
|
|
6859
|
+
4. **Connect an orchestrator** -- deploy an orchestrator and connect it with a join token from **Settings → Orchestrator keys**.
|
|
6860
|
+
5. **Add a webhook source** -- register a source under **Settings → Sources** so pushes and pull requests trigger runs.
|
|
6861
|
+
6. **Trigger your first run** -- push to your repository to produce your first run through the relay.
|
|
6862
|
+
|
|
6157
6863
|
## Navigation
|
|
6158
6864
|
|
|
6159
6865
|
### Sidebar
|
|
@@ -6161,6 +6867,7 @@ The KiCI dashboard is a web-based interface for monitoring workflow runs, inspec
|
|
|
6161
6867
|
The left sidebar provides persistent navigation across all org-scoped pages:
|
|
6162
6868
|
|
|
6163
6869
|
- **Org switcher** -- dropdown at the top to switch between organizations
|
|
6870
|
+
- **Getting started** -- onboarding checklist (shows a `done/total` badge until complete or dismissed)
|
|
6164
6871
|
- **Runs** -- the default landing page, showing your workflow run history
|
|
6165
6872
|
- **Workflows** -- permanently registered workflows listening for events
|
|
6166
6873
|
- **Diagnostics** -- infrastructure health, execution metrics, and recent errors
|
|
@@ -6438,7 +7145,7 @@ The job tree supports keyboard navigation:
|
|
|
6438
7145
|
The content area has the following tabs:
|
|
6439
7146
|
|
|
6440
7147
|
- **Logs** (default) -- shows log output for the selected job or step
|
|
6441
|
-
- **Payload** -- webhook payload viewer showing the raw event payload that triggered the run
|
|
7148
|
+
- **Payload** -- webhook payload viewer showing the raw event payload that triggered the run. This tab appears only for runs triggered by a webhook event (and re-runs of those, which copy the original payload); runs started by a schedule, manual schedule, lifecycle event, or another run carry no payload, so the tab is hidden for them
|
|
6442
7149
|
- **Timeline** -- CSS Gantt chart showing the execution timeline of all jobs, with percentage-based bars and striped animation for running jobs. A **Provisioning** milestones section between the dispatch and execution phases plots scaler lifecycle events for the run — including a **Provisioning failed** marker when the scaler could not bring an agent up
|
|
6443
7150
|
- **Summary** -- contextual overview scoped to the current selection (run-level trigger/repo/timing info, or job-level execution context with environment variables, runtime info, and sandbox details)
|
|
6444
7151
|
|
|
@@ -6553,7 +7260,7 @@ Each member's linked provider accounts (e.g. GitHub) are also visible here.
|
|
|
6553
7260
|
|
|
6554
7261
|
<!-- help:settings-roles#settings -->
|
|
6555
7262
|
|
|
6556
|
-
Roles define granular permissions across
|
|
7263
|
+
Roles define granular permissions across 15 resource categories (runs, secrets, members, etc.) with 5 access levels: `none`, `read`, `read_payload`, `write`, `admin`.
|
|
6557
7264
|
|
|
6558
7265
|
Create custom roles to restrict what team members can do, or use the built-in **Owner** role for full access.
|
|
6559
7266
|
|
|
@@ -6565,6 +7272,8 @@ API keys allow programmatic access to the KiCI API for automation, scripts, and
|
|
|
6565
7272
|
|
|
6566
7273
|
Each key is scoped to this organization with a custom permission matrix and an optional expiry date. Keys can be revoked individually.
|
|
6567
7274
|
|
|
7275
|
+
Use a key's clone button to open the creation modal prefilled with that key's name, expiry, and permissions — handy for recreating an expired key or deriving a new key from an existing one.
|
|
7276
|
+
|
|
6568
7277
|
<!-- /help:settings-api-keys -->
|
|
6569
7278
|
|
|
6570
7279
|
<!-- help:settings-orchestrator-keys#orchestrator-keys -->
|
|
@@ -6573,6 +7282,8 @@ Orchestrator keys authenticate the WebSocket connection between your orchestrato
|
|
|
6573
7282
|
|
|
6574
7283
|
Create a key here and set it as the `KICI_PLATFORM_TOKEN` environment variable in your orchestrator configuration. Keys can optionally be restricted to specific routing patterns.
|
|
6575
7284
|
|
|
7285
|
+
Use a key's clone button to open the creation modal prefilled with that key's name and description.
|
|
7286
|
+
|
|
6576
7287
|
<!-- /help:settings-orchestrator-keys -->
|
|
6577
7288
|
|
|
6578
7289
|
<!-- help:settings-sources#sources -->
|
|
@@ -6625,6 +7336,8 @@ The relayed-webhooks counter only includes webhooks delivered through the KiCI P
|
|
|
6625
7336
|
|
|
6626
7337
|
Webhooks pointed directly at your orchestrator's public ingest endpoint never reach the Platform, so they're invisible to this counter and uncapped on every Hosted tier. If you have a public orchestrator ingress, you can mix-and-match: use the relay for sources you can't expose publicly, and point GitHub (or any provider / generic webhook) straight at your orchestrator for the rest.
|
|
6627
7338
|
|
|
7339
|
+
Every webhook the relay forwards counts — **including ones your workflows ultimately ignore**. Trigger matching runs on your orchestrator, not on the Platform, so the relay forwards each signature-verified webhook before any trigger is evaluated. A source that sends many events you filter down to a handful of runs still consumes one relayed webhook per event. If a high-volume source mostly produces no run, point it directly at your orchestrator (see above) to keep it off this counter entirely.
|
|
7340
|
+
|
|
6628
7341
|
When you hit the cap, new relayed webhooks are rejected with `429 Plan limit reached`. Upgrade in the Stripe Billing Portal to lift the cap immediately; usage resets monthly on your billing anniversary.
|
|
6629
7342
|
|
|
6630
7343
|
<!-- /help:settings-billing-relayed-webhooks -->
|
|
@@ -6794,6 +7507,20 @@ The summary strip at the top shows total / enabled / disabled counts plus whethe
|
|
|
6794
7507
|
|
|
6795
7508
|
<!-- /help:settings-security-dashboard-policy -->
|
|
6796
7509
|
|
|
7510
|
+
<!-- help:settings-support-access#settings -->
|
|
7511
|
+
|
|
7512
|
+
Controls whether KiCI support staff may open read-only support sessions against your organization. Sessions are **off by default** — nobody outside your org can read your data until you opt in here.
|
|
7513
|
+
|
|
7514
|
+
When enabled:
|
|
7515
|
+
|
|
7516
|
+
- KiCI staff can open time-boxed, read-only sessions to investigate an issue.
|
|
7517
|
+
- Every read they perform is recorded in your audit trail with the support reason.
|
|
7518
|
+
- No writes are ever possible during a session.
|
|
7519
|
+
|
|
7520
|
+
Disabling the toggle immediately ends any in-progress support session. Only users with the `support:admin` permission (owners by default) can change this setting.
|
|
7521
|
+
|
|
7522
|
+
<!-- /help:settings-support-access -->
|
|
7523
|
+
|
|
6797
7524
|
<!-- help:settings-webhooks-delivery-log#settings -->
|
|
6798
7525
|
|
|
6799
7526
|
The delivery log shows recent webhook deliveries for an endpoint, including the HTTP status code, number of retry attempts, and the event payload.
|
|
@@ -6850,11 +7577,26 @@ The settings page (`/orgs/:customerId/settings`) uses a tabbed layout:
|
|
|
6850
7577
|
10. **Webhooks** -- outbound webhook endpoint management with delivery logs and test ping
|
|
6851
7578
|
11. **Event log** -- inbound webhook delivery log (visible with `event_log:read` permission)
|
|
6852
7579
|
12. **Security** -- read-only view of the orchestrator's dashboard-write policy matrix (visible with `org_settings:read` permission)
|
|
7580
|
+
13. **Support access** -- opt-in switch that controls whether KiCI support staff may open read-only support sessions against your org (visible with `support:read`; toggled with `support:admin`)
|
|
6853
7581
|
|
|
6854
7582
|
Audit-log-style entries are no longer a settings tab; they live on the dedicated **Activity** page accessible from the sidebar.
|
|
6855
7583
|
|
|
6856
7584
|
Tab selection syncs with the URL path (`/settings/members`, `/settings/api-keys`, etc.), making tabs bookmarkable.
|
|
6857
7585
|
|
|
7586
|
+
### Support access
|
|
7587
|
+
|
|
7588
|
+
The Support access tab controls whether KiCI support staff may open a read-only **support session** against your organization to help diagnose an issue. The setting is **off by default** -- until you opt in here, no one outside your org can read your data.
|
|
7589
|
+
|
|
7590
|
+
When support access is enabled:
|
|
7591
|
+
|
|
7592
|
+
- A KiCI operator can open a time-boxed (30-minute, renewable), read-only support session scoped to a stated reason.
|
|
7593
|
+
- A support session is **runs-only**: the operator can browse your run list and, by confirming each run individually, view that run's detail and step logs. Nothing else is visible, and no write is ever possible.
|
|
7594
|
+
- Every run an operator opens is recorded in your [Activity](#activity) audit trail, attributed to the operator with the support reason -- so you can see exactly what was looked at and why.
|
|
7595
|
+
|
|
7596
|
+
**Disabling immediately ends any active session.** Toggling the switch off closes every in-progress support session for your org at once. Enabling and disabling the setting is itself audited, attributed to the user who changed it.
|
|
7597
|
+
|
|
7598
|
+
Viewing the setting requires the `support:read` permission; changing it requires `support:admin` (granted to owners by default).
|
|
7599
|
+
|
|
6858
7600
|
### Orchestrator keys
|
|
6859
7601
|
|
|
6860
7602
|
The orchestrator keys tab manages API keys used to authenticate orchestrator-to-Platform WebSocket connections. These are separate from user API keys (which grant dashboard/API access).
|
|
@@ -6931,6 +7673,8 @@ Personal access tokens (PATs) are long-lived credentials for programmatic API ac
|
|
|
6931
7673
|
|
|
6932
7674
|
Create a PAT to authenticate CLI tools or scripts without going through the OIDC login flow. Tokens can be revoked at any time.
|
|
6933
7675
|
|
|
7676
|
+
Use a token's clone button to open the creation modal prefilled with that token's name, expiry, and permissions.
|
|
7677
|
+
|
|
6934
7678
|
<!-- /help:personal-pats -->
|
|
6935
7679
|
|
|
6936
7680
|
<!-- help:personal-linked-accounts#account -->
|
|
@@ -7029,7 +7773,7 @@ The policy state is visible at three layers in the UI:
|
|
|
7029
7773
|
- A **lock-icon prefix** on every disabled control, with a per-control CLI hint.
|
|
7030
7774
|
- A **per-page banner** on any page containing at least one disabled operation, listing every disabled op on that page and its CLI equivalent.
|
|
7031
7775
|
|
|
7032
|
-
The Security policy page (Settings → Security → Dashboard policy) renders the full
|
|
7776
|
+
The Security policy page (Settings → Security → Dashboard policy) renders the full 24-row read-only matrix with the current state and the `kici-admin` command for each row. The policy itself cannot be changed from the dashboard — the orchestrator operator manages it via `kici-admin org-settings dashboard-writes`. See [Dashboard-write policy](/operator/security/dashboard-write-policy) for the operator-side details.
|
|
7033
7777
|
|
|
7034
7778
|
## Approval queue
|
|
7035
7779
|
|
|
@@ -7107,16 +7851,27 @@ The dashboard shows informative error pages instead of blank screens:
|
|
|
7107
7851
|
|
|
7108
7852
|
## Dynamic values
|
|
7109
7853
|
|
|
7110
|
-
Source: https://kici.dev/
|
|
7854
|
+
Source: https://docs.kici.dev/user/dynamic-values/
|
|
7111
7855
|
|
|
7112
|
-
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
|
|
7856
|
+
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.
|
|
7113
7857
|
|
|
7114
7858
|
```typescript
|
|
7115
7859
|
job('deploy', {
|
|
7116
7860
|
runsOn: ['default'],
|
|
7117
|
-
environment: (event) => event.
|
|
7118
|
-
env: (event) => ({ BRANCH: event.
|
|
7119
|
-
concurrencyGroup: (event) => `deploy-${event.
|
|
7861
|
+
environment: (event) => event.targetBranch,
|
|
7862
|
+
env: (event) => ({ BRANCH: event.targetBranch }),
|
|
7863
|
+
concurrencyGroup: (event) => `deploy-${event.targetBranch}`,
|
|
7864
|
+
steps: [
|
|
7865
|
+
/* ... */
|
|
7866
|
+
],
|
|
7867
|
+
});
|
|
7868
|
+
```
|
|
7869
|
+
|
|
7870
|
+
```typescript
|
|
7871
|
+
job('deploy', {
|
|
7872
|
+
runsOn: 'default',
|
|
7873
|
+
// One shape everywhere: branch on the normalized event type.
|
|
7874
|
+
environment: (event) => (event.type === 'pull_request' ? 'preview' : 'production'),
|
|
7120
7875
|
steps: [
|
|
7121
7876
|
/* ... */
|
|
7122
7877
|
],
|
|
@@ -7143,21 +7898,21 @@ When the compiler detects a pure function, it serializes the function source dir
|
|
|
7143
7898
|
**Examples of pure functions:**
|
|
7144
7899
|
|
|
7145
7900
|
```typescript
|
|
7146
|
-
// Simple branch extraction
|
|
7147
|
-
environment: (event) => event.
|
|
7901
|
+
// Simple branch extraction
|
|
7902
|
+
environment: (event) => event.targetBranch;
|
|
7148
7903
|
|
|
7149
7904
|
// Object literal with string operations
|
|
7150
|
-
env: (
|
|
7905
|
+
env: (event) => ({ BRANCH: event.targetBranch });
|
|
7151
7906
|
|
|
7152
7907
|
// Concatenation with event data
|
|
7153
|
-
concurrencyGroup: (event) => `deploy-${event.
|
|
7908
|
+
concurrencyGroup: (event) => `deploy-${event.targetBranch}`;
|
|
7154
7909
|
|
|
7155
7910
|
// Using safe globals
|
|
7156
|
-
env: (event) => ({ UPPER: String(event.
|
|
7911
|
+
env: (event) => ({ UPPER: String(event.targetBranch).toUpperCase() });
|
|
7157
7912
|
|
|
7158
7913
|
// Local variables are fine
|
|
7159
7914
|
environment: (event) => {
|
|
7160
|
-
const parts = event.
|
|
7915
|
+
const parts = event.targetBranch.split('/');
|
|
7161
7916
|
return parts[parts.length - 1];
|
|
7162
7917
|
};
|
|
7163
7918
|
```
|
|
@@ -7177,7 +7932,7 @@ This adds approximately 5-10 seconds of overhead for cloning and evaluation.
|
|
|
7177
7932
|
|
|
7178
7933
|
```typescript
|
|
7179
7934
|
// Async functions cannot be inlined
|
|
7180
|
-
environment: async (event) => await lookupEnv(event.
|
|
7935
|
+
environment: async (event) => await lookupEnv(event.targetBranch);
|
|
7181
7936
|
|
|
7182
7937
|
// External module references
|
|
7183
7938
|
env: (event) => {
|
|
@@ -7197,35 +7952,35 @@ env: async (event) => {
|
|
|
7197
7952
|
|
|
7198
7953
|
## Performance comparison
|
|
7199
7954
|
|
|
7200
|
-
| Evaluation path | Overhead | When used
|
|
7201
|
-
| ------------------------------------ | -------- |
|
|
7202
|
-
| Static value (string/object literal) | ~0ms | `environment: 'staging'`
|
|
7203
|
-
| Inline expression (pure function) | ~0ms | `environment: (event) => event.
|
|
7204
|
-
| Init job (impure function) | ~5-10s | `environment: async (event) => await lookupEnv(event.
|
|
7955
|
+
| Evaluation path | Overhead | When used |
|
|
7956
|
+
| ------------------------------------ | -------- | ------------------------------------------------------------------- |
|
|
7957
|
+
| Static value (string/object literal) | ~0ms | `environment: 'staging'` |
|
|
7958
|
+
| Inline expression (pure function) | ~0ms | `environment: (event) => event.targetBranch` |
|
|
7959
|
+
| Init job (impure function) | ~5-10s | `environment: async (event) => await lookupEnv(event.targetBranch)` |
|
|
7205
7960
|
|
|
7206
7961
|
## Tips
|
|
7207
7962
|
|
|
7208
7963
|
- **Write pure functions whenever possible** to avoid the init-job delay. Most environment and env computations only need the event payload data.
|
|
7209
7964
|
- **Check compiler warnings** -- the compiler tells you when a function is classified as impure and explains why.
|
|
7210
7965
|
- **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.
|
|
7211
|
-
- **The event parameter
|
|
7966
|
+
- **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](./sdk/event-payloads.md) 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`, …).
|
|
7212
7967
|
|
|
7213
7968
|
---
|
|
7214
7969
|
|
|
7215
7970
|
## Environment variables
|
|
7216
7971
|
|
|
7217
|
-
Source: https://kici.dev/
|
|
7972
|
+
Source: https://docs.kici.dev/user/env-vars/
|
|
7218
7973
|
|
|
7219
|
-
The KiCI CLI reads the following environment variables to customize its behavior. OAuth login (`kici login` without `--token`)
|
|
7974
|
+
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 a self-hosted Platform or a testing environment.
|
|
7220
7975
|
|
|
7221
7976
|
## Authentication
|
|
7222
7977
|
|
|
7223
|
-
| Variable | Description
|
|
7224
|
-
| --------------------- |
|
|
7225
|
-
| `KICI_OIDC_ISSUER` | OIDC issuer URL for authentication
|
|
7226
|
-
| `KICI_OIDC_CLIENT_ID` | OIDC client ID for the CLI application
|
|
7227
|
-
| `KICI_PLATFORM_URL` | Platform API base URL
|
|
7228
|
-
| `KICI_CONFIG_DIR` | Override the KiCI config directory
|
|
7978
|
+
| Variable | Description | Default |
|
|
7979
|
+
| --------------------- | -------------------------------------- | -------------------------------------------- |
|
|
7980
|
+
| `KICI_OIDC_ISSUER` | OIDC issuer URL for authentication | `https://auth.kici.dev/realms/kici-internal` |
|
|
7981
|
+
| `KICI_OIDC_CLIENT_ID` | OIDC client ID for the CLI application | `kici-cli` |
|
|
7982
|
+
| `KICI_PLATFORM_URL` | Platform API base URL | `https://api.kici.dev` |
|
|
7983
|
+
| `KICI_CONFIG_DIR` | Override the KiCI config directory | `~/.kici` |
|
|
7229
7984
|
|
|
7230
7985
|
## Browser behavior
|
|
7231
7986
|
|
|
@@ -7251,9 +8006,9 @@ Authenticate with a pre-existing API key (no browser needed):
|
|
|
7251
8006
|
kici login --token <<< "$KICI_API_KEY"
|
|
7252
8007
|
```
|
|
7253
8008
|
|
|
7254
|
-
###
|
|
8009
|
+
### Self-hosted Platform or custom OIDC provider
|
|
7255
8010
|
|
|
7256
|
-
|
|
8011
|
+
`kici login` targets the hosted KiCI Platform by default. To point the CLI at a self-hosted Platform or a testing OIDC provider, override the defaults:
|
|
7257
8012
|
|
|
7258
8013
|
```bash
|
|
7259
8014
|
export KICI_OIDC_ISSUER=https://your-idp.example.com
|
|
@@ -7295,7 +8050,7 @@ kici login
|
|
|
7295
8050
|
|
|
7296
8051
|
## Environments
|
|
7297
8052
|
|
|
7298
|
-
Source: https://kici.dev/
|
|
8053
|
+
Source: https://docs.kici.dev/user/environments/
|
|
7299
8054
|
|
|
7300
8055
|
<!-- help:environments-list#overview -->
|
|
7301
8056
|
|
|
@@ -7360,12 +8115,12 @@ export default workflow('deploy', {
|
|
|
7360
8115
|
|
|
7361
8116
|
### Dynamic environments
|
|
7362
8117
|
|
|
7363
|
-
The environment name can be a string or a function (sync or async) for dynamic environments (e.g., per-PR review environments)
|
|
8118
|
+
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`:
|
|
7364
8119
|
|
|
7365
8120
|
```typescript
|
|
7366
8121
|
job('deploy-review', {
|
|
7367
8122
|
runsOn: 'default',
|
|
7368
|
-
environment:
|
|
8123
|
+
environment: (event) => `review/PR-${event.payload.pull_request.number}`,
|
|
7369
8124
|
steps: [
|
|
7370
8125
|
step('deploy', async (ctx) => {
|
|
7371
8126
|
// ctx.environment is 'review/PR-123' (resolved at runtime)
|
|
@@ -7375,7 +8130,7 @@ job('deploy-review', {
|
|
|
7375
8130
|
});
|
|
7376
8131
|
```
|
|
7377
8132
|
|
|
7378
|
-
Dynamic environments that match a glob pattern (e.g., `review/*`) inherit the pattern's configuration, variables, and protection rules.
|
|
8133
|
+
A pure function like the one above (see [Dynamic values](dynamic-values.md)) 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.
|
|
7379
8134
|
|
|
7380
8135
|
### Job-level environment variables
|
|
7381
8136
|
|
|
@@ -7387,7 +8142,7 @@ job('deploy', {
|
|
|
7387
8142
|
environment: 'production',
|
|
7388
8143
|
env: { DEPLOY_TARGET: 'us-east-1' },
|
|
7389
8144
|
// Or dynamic:
|
|
7390
|
-
// env:
|
|
8145
|
+
// env: (event) => ({ DEPLOY_SHA: event.payload.after?.slice(0, 7) }),
|
|
7391
8146
|
steps: [
|
|
7392
8147
|
step('deploy', async (ctx) => {
|
|
7393
8148
|
// DEPLOY_TARGET is available in ctx.env
|
|
@@ -7409,7 +8164,7 @@ job('deploy', {
|
|
|
7409
8164
|
environment: 'production',
|
|
7410
8165
|
concurrencyGroup: 'production-api',
|
|
7411
8166
|
// Or dynamic:
|
|
7412
|
-
// concurrencyGroup:
|
|
8167
|
+
// concurrencyGroup: (event) => `review-${event.payload.pull_request.number}`,
|
|
7413
8168
|
steps: [
|
|
7414
8169
|
/* ... */
|
|
7415
8170
|
],
|
|
@@ -7536,10 +8291,12 @@ Strategy: queue (or cancel-pending)
|
|
|
7536
8291
|
|
|
7537
8292
|
### Creating environments
|
|
7538
8293
|
|
|
7539
|
-
Navigate to **Settings > Environments** in the dashboard. Click
|
|
8294
|
+
Navigate to **Settings > Environments** in the dashboard. Click **New environment** to choose the environment name and type (Fixed or Glob).
|
|
8295
|
+
|
|
8296
|
+
- **Fixed** -- applies to jobs that declare exactly this environment name, like `staging` or `production`
|
|
8297
|
+
- **Glob** -- applies to any environment name a job declares that matches the pattern, e.g. `review/*` matches a job with `environment: 'review/PR-123'`
|
|
7540
8298
|
|
|
7541
|
-
|
|
7542
|
-
- **Glob** -- a pattern like `review/*` that matches dynamic environment names
|
|
8299
|
+
The environments list shows each environment's type, whether test runs may use it (the `allowLocalExecution` flag -- see the [testing guide](./testing-guide.md)), and whether it is enabled.
|
|
7543
8300
|
|
|
7544
8301
|
### Environment detail page
|
|
7545
8302
|
|
|
@@ -7584,7 +8341,7 @@ interface EnvironmentSecrets {
|
|
|
7584
8341
|
|
|
7585
8342
|
## Event system
|
|
7586
8343
|
|
|
7587
|
-
Source: https://kici.dev/
|
|
8344
|
+
Source: https://docs.kici.dev/user/events/
|
|
7588
8345
|
|
|
7589
8346
|
KiCI supports two broad categories of workflow triggers: **git-based triggers** that work immediately, and **event-based triggers** that use a registration model. Understanding this distinction is key to working effectively with non-git triggers like schedules, custom events, and generic webhooks.
|
|
7590
8347
|
|
|
@@ -7780,7 +8537,7 @@ export default workflow('nightly-build', {
|
|
|
7780
8537
|
on: schedule({ cron: '0 2 * * *' }),
|
|
7781
8538
|
jobs: [
|
|
7782
8539
|
job('build', {
|
|
7783
|
-
runsOn: '
|
|
8540
|
+
runsOn: 'linux',
|
|
7784
8541
|
steps: [
|
|
7785
8542
|
step('build', async ({ $ }) => {
|
|
7786
8543
|
await $`pnpm build`;
|
|
@@ -7879,7 +8636,7 @@ export default workflow('build', {
|
|
|
7879
8636
|
on: push({ branches: 'main' }),
|
|
7880
8637
|
jobs: [
|
|
7881
8638
|
job('build', {
|
|
7882
|
-
runsOn: '
|
|
8639
|
+
runsOn: 'linux',
|
|
7883
8640
|
steps: [
|
|
7884
8641
|
step('build', async ({ $ }) => {
|
|
7885
8642
|
await $`pnpm build`;
|
|
@@ -7934,7 +8691,7 @@ export default workflow('post-deploy', {
|
|
|
7934
8691
|
on: kiciEvent({ name: 'deploy-complete', match: { '$.env': 'prod' } }),
|
|
7935
8692
|
jobs: [
|
|
7936
8693
|
job('smoke-test', {
|
|
7937
|
-
runsOn: '
|
|
8694
|
+
runsOn: 'linux',
|
|
7938
8695
|
steps: [
|
|
7939
8696
|
step('test', async ({ $ }) => {
|
|
7940
8697
|
await $`./scripts/smoke-test.sh`;
|
|
@@ -7959,7 +8716,7 @@ Custom events are delivered immediately when emitted (mid-workflow, not queued u
|
|
|
7959
8716
|
|
|
7960
8717
|
## Global workflows
|
|
7961
8718
|
|
|
7962
|
-
Source: https://kici.dev/
|
|
8719
|
+
Source: https://docs.kici.dev/user/global-workflows/
|
|
7963
8720
|
|
|
7964
8721
|
Global workflows let one **workflow repo** define jobs that run on events from many **source repos** in the same org. They're the answer to "I want one CI policy / release pipeline / security scan to fire on every repo without copy-pasting `.kici/` folders everywhere."
|
|
7965
8722
|
|
|
@@ -8081,7 +8838,7 @@ Non-push triggers work too — `pr()`, `tag()`, `comment()`, `release()`, `workf
|
|
|
8081
8838
|
|
|
8082
8839
|
## Private npm registries
|
|
8083
8840
|
|
|
8084
|
-
Source: https://kici.dev/
|
|
8841
|
+
Source: https://docs.kici.dev/user/private-registries/
|
|
8085
8842
|
|
|
8086
8843
|
A workflow's `.kici/package.json` may depend on packages published to a private registry — your org's internal CodeArtifact, a GitHub Packages scope, a self-hosted Verdaccio, JFrog, Cloudsmith, GitLab, etc. KiCI ships two ways to authenticate `npm install` against those registries from inside a job, plus an escape hatch for short-lived tokens.
|
|
8087
8844
|
|
|
@@ -8222,7 +8979,7 @@ export default workflow('build', {
|
|
|
8222
8979
|
});
|
|
8223
8980
|
```
|
|
8224
8981
|
|
|
8225
|
-
The same pattern works for GCP Artifact Registry — replace the `aws codeartifact` call with `gcloud auth print-access-token`.
|
|
8982
|
+
The same pattern works for GCP Artifact Registry — replace the `aws codeartifact` call with `gcloud auth print-access-token`. The manual setup-step shown here is the supported path for these short-lived flows.
|
|
8226
8983
|
|
|
8227
8984
|
## Provider-specific examples
|
|
8228
8985
|
|
|
@@ -8329,7 +9086,7 @@ The dashboard JSON lives at `infra/terraform/modules/grafana/dashboards/install-
|
|
|
8329
9086
|
|
|
8330
9087
|
## Secrets
|
|
8331
9088
|
|
|
8332
|
-
Source: https://kici.dev/
|
|
9089
|
+
Source: https://docs.kici.dev/user/secrets/
|
|
8333
9090
|
|
|
8334
9091
|
KiCI provides an explicit secrets API that gives workflow steps controlled access to secrets stored in the orchestrator's secret store. Secrets are never auto-injected into `process.env` -- you must explicitly request each secret by name.
|
|
8335
9092
|
|
|
@@ -8595,7 +9352,7 @@ See [CLI reference](/user/cli) for the `kici types` command.
|
|
|
8595
9352
|
|
|
8596
9353
|
## GitHub App provider
|
|
8597
9354
|
|
|
8598
|
-
Source: https://kici.dev/
|
|
9355
|
+
Source: https://docs.kici.dev/user/providers/github/
|
|
8599
9356
|
|
|
8600
9357
|
The **GitHub App** is KiCI's flagship source. A single App:
|
|
8601
9358
|
|
|
@@ -8750,7 +9507,7 @@ kici-admin source get-webhook-secret github:12345 # Fetch the secret (for debug
|
|
|
8750
9507
|
```
|
|
8751
9508
|
|
|
8752
9509
|
For the full CLI reference see the `source` section of the
|
|
8753
|
-
[kici-admin CLI reference](../../operator/kici-admin-cli.md).
|
|
9510
|
+
[kici-admin CLI reference](../../operator/orchestrator/kici-admin-cli.md).
|
|
8754
9511
|
|
|
8755
9512
|
## Routing keys
|
|
8756
9513
|
|
|
@@ -8903,7 +9660,7 @@ and confirm the App is installed on that repo.
|
|
|
8903
9660
|
Gogs / GitLab, and for plain-GitHub repos without an App
|
|
8904
9661
|
- [GitHub checks architecture](../../architecture/webhooks/github-checks.md)
|
|
8905
9662
|
- [Global workflows](../../architecture/global-workflows.md)
|
|
8906
|
-
- [kici-admin CLI reference](../../operator/kici-admin-cli.md)
|
|
9663
|
+
- [kici-admin CLI reference](../../operator/orchestrator/kici-admin-cli.md)
|
|
8907
9664
|
- [Event routing](../../operator/event-routing.md) — operator-level
|
|
8908
9665
|
routing-key mechanics
|
|
8909
9666
|
|
|
@@ -8911,7 +9668,7 @@ and confirm the App is installed on that repo.
|
|
|
8911
9668
|
|
|
8912
9669
|
## Universal-git provider
|
|
8913
9670
|
|
|
8914
|
-
Source: https://kici.dev/
|
|
9671
|
+
Source: https://docs.kici.dev/user/providers/universal-git/
|
|
8915
9672
|
|
|
8916
9673
|
The **universal-git** provider lets KiCI treat any git forge that speaks a
|
|
8917
9674
|
GitHub-shaped webhook payload as a first-class source. That covers Forgejo,
|
|
@@ -9152,11 +9909,11 @@ with the right PEM.
|
|
|
9152
9909
|
|
|
9153
9910
|
## Data flows
|
|
9154
9911
|
|
|
9155
|
-
Source: https://kici.dev/
|
|
9912
|
+
Source: https://docs.kici.dev/architecture/data-flows/
|
|
9156
9913
|
|
|
9157
9914
|
This document describes the key data flows through the KiCI architecture: webhook delivery, job execution, dependency caching, re-run and cancel, trace ID propagation, internal event routing, and generic webhook ingestion.
|
|
9158
9915
|
|
|
9159
|
-
> **Lock file schema version:** The lock file uses schema version
|
|
9916
|
+
> **Lock file schema version:** The lock file uses schema version 15, which adds per-job init config on top of v14's declarative cache specs, v11's `LockInlineValue` for pure function inline evaluation, v10's simplified negative patterns (! prefix in repos/paths arrays), v9's global workflow repos matching, and v8's runsOn polymorphic type support.
|
|
9160
9917
|
|
|
9161
9918
|
## Webhook delivery flow
|
|
9162
9919
|
|
|
@@ -9182,7 +9939,7 @@ GitHub --> Platform Relay --> Orchestrator --> Agent
|
|
|
9182
9939
|
10. **Orchestrator fetches lock file** via the provider's `LockFileFetcher` (cached with LRU). For untrusted PR events, fetches both base and head lock files in parallel; for trusted PRs and pushes, fetches from head SHA.
|
|
9183
9940
|
11. **Orchestrator detects workflow modifications** for untrusted PR events by comparing base and head lock files via `detectWorkflowModifications()`, applying security holds when non-trusted contributors modify workflow files.
|
|
9184
9941
|
12. **Orchestrator extracts registrations** on default-branch pushes: persists registerable workflows (event, schedule, lifecycle triggers) for cluster-wide event matching.
|
|
9185
|
-
13. **Orchestrator
|
|
9942
|
+
13. **Orchestrator notifies the event router** on default-branch pushes: after the registrations are persisted, emits a `registration.updated` event via `eventRouter.emit()` (if event routing is active). Workflow event subscriptions are the persisted registrations themselves, matched at emit time through the registration index.
|
|
9186
9943
|
14. **Orchestrator fetches changed files** via the provider's `ChangedFilesFetcher` for path-based trigger filtering (skipped when no workflow uses path filters).
|
|
9187
9944
|
15. **Orchestrator matches triggers** against lock file using `matchAllWorkflows()` from `@kici-dev/engine`.
|
|
9188
9945
|
16. **Orchestrator checks caches** for source tarballs and dependency tarballs.
|
|
@@ -9325,9 +10082,9 @@ The source cache and dep cache are independent. Four combinations are possible:
|
|
|
9325
10082
|
|
|
9326
10083
|
Dep cache misses alone do **not** trigger a build job. Deps are platform-specific (`deps/{platform}-{arch}/{hash}.tar.gz`) so a build job would need a builder agent matching the target platform, which may not exist (e.g., an arm64 builder when only x64 builders are available). When the source cache misses, the dispatched build job piggy-backs dep packing if deps are also missing. A single build job handles both artifacts when both miss, avoiding duplicate builds.
|
|
9327
10084
|
|
|
9328
|
-
###
|
|
10085
|
+
### Cross-source / no-contentHash workflows
|
|
9329
10086
|
|
|
9330
|
-
- **Lock files without `contentHash`** (schema v1) skip the source cache entirely; agents compile from source. Regenerate lock files with `kici compile` to enable caching. The current lock file schema version is
|
|
10087
|
+
- **Lock files without `contentHash`** (schema v1) skip the source cache entirely; agents compile from source. Regenerate lock files with `kici compile` to enable caching. The current lock file schema version is 15.
|
|
9331
10088
|
- **Cross-source / global-workflow dispatch** (a workflow registered against source A fired by a webhook on source B) bypasses both caches. The registration's lock file entry still carries `contentHash`, but the cross-source path always clone-and-installs — the eval temp dir doesn't ship `@kici-dev/sdk`. The execution agent still verifies `contentHash` against the cloned source for drift detection.
|
|
9332
10089
|
|
|
9333
10090
|
### Build deduplication
|
|
@@ -9411,6 +10168,64 @@ The two-phase metadata approach (`upload via PUT` then `initMeta via CopyObject`
|
|
|
9411
10168
|
|
|
9412
10169
|
Agents receive pre-signed S3 GET URLs (15-minute expiry) directly in `job.dispatch` messages. Agents download artifacts from S3, bypassing the orchestrator for all data transfer.
|
|
9413
10170
|
|
|
10171
|
+
## User-facing cache flow
|
|
10172
|
+
|
|
10173
|
+
The source/dep cache above is internal: the orchestrator owns its keys and decides when to hit or build. The **user-facing cache** is driven by the workflow author — the declarative `cache: { key, paths, restoreKeys? }` on a job/step, or the imperative `ctx.cache.restore()` / `ctx.cache.save()` API (see [SDK caching reference](../user/sdk/caching.md)). It reuses the same object-storage backend and the same direct-to-storage presigned-URL transport, but the agent — not the orchestrator — initiates each restore and save over WebSocket.
|
|
10174
|
+
|
|
10175
|
+
The agent's cache module archives `paths` into a gzipped tarball (computing a SHA-256 over the bytes) and streams downloads back through a checksum-verified extract pipeline. The orchestrator's `UserCache` owns the `cache/<orgId>/<repoId>/<scope>/<key>` namespacing, the immutable first-save check, the `restoreKeys` prefix scan, the two-phase atomic save, and per-org quota/TTL eviction.
|
|
10176
|
+
|
|
10177
|
+
### Restore flow
|
|
10178
|
+
|
|
10179
|
+
```
|
|
10180
|
+
Agent Orchestrator (UserCache) Object storage
|
|
10181
|
+
| | |
|
|
10182
|
+
|-- cache.user.restore.request ------->| |
|
|
10183
|
+
| { key, restoreKeys? } |-- exact key in read prefixes ->|
|
|
10184
|
+
| | (isolated: iso/<runId>/ |
|
|
10185
|
+
| | then shared/; trusted: |
|
|
10186
|
+
| | shared/ only) |
|
|
10187
|
+
| |-- restoreKeys prefix scan ---->|
|
|
10188
|
+
| | (newest match wins) |
|
|
10189
|
+
| |-- getUrl(matched) + touch ---->|
|
|
10190
|
+
|<-- cache.user.restore.response ------| |
|
|
10191
|
+
| { hit, matchedKey?, | |
|
|
10192
|
+
| downloadUrl?, tarHash? } | |
|
|
10193
|
+
| | |
|
|
10194
|
+
|-- HTTP GET (tarball body) -------------------------------------->|
|
|
10195
|
+
| (direct download; verify tarHash, extract paths) |
|
|
10196
|
+
```
|
|
10197
|
+
|
|
10198
|
+
The restore resolves the exact `key` across the ref's read prefixes first, then each `restoreKeys` prefix in order (newest matching entry wins). A trusted ref reads only `shared/`; an untrusted/fork ref reads its own `iso/<runId>/` scope and then falls back to `shared/`. On a hit the response carries a presigned GET URL plus the tarball's `tarHash`, which the agent verifies before extracting.
|
|
10199
|
+
|
|
10200
|
+
### Save flow (two-phase atomic)
|
|
10201
|
+
|
|
10202
|
+
```
|
|
10203
|
+
Agent Orchestrator (UserCache) Object storage
|
|
10204
|
+
| | |
|
|
10205
|
+
|-- cache.user.save.request --------->| |
|
|
10206
|
+
| { key } |-- has(final key)? ------------>|
|
|
10207
|
+
| | (immutable: skip if exists) |
|
|
10208
|
+
| |-- getUploadUrl(.tmp-<uuid>) -->|
|
|
10209
|
+
|<-- cache.user.save.response ---------| |
|
|
10210
|
+
| { uploadUrl?, skip } | |
|
|
10211
|
+
| | |
|
|
10212
|
+
|-- HTTP PUT (tarball body) ----------------------------------->|
|
|
10213
|
+
| (direct upload to temp object) |
|
|
10214
|
+
| | |
|
|
10215
|
+
|-- cache.user.save.complete -------->| |
|
|
10216
|
+
| { key, tarHash, sizeBytes } |-- copy(temp -> final) -------->|
|
|
10217
|
+
| |-- delete(temp) --------------->|
|
|
10218
|
+
| |-- initMeta(final) ------------>|
|
|
10219
|
+
| |-- put(.hash) + put(.size) ---->|
|
|
10220
|
+
| |-- enforce per-org quota ------>|
|
|
10221
|
+
```
|
|
10222
|
+
|
|
10223
|
+
The save is **immutable** and **atomic**. The orchestrator declines (`skip: true`) up front if the exact key already exists. Otherwise the agent uploads to a `.tmp-<uuid>` object via a presigned PUT, then `cache.user.save.complete` triggers a server-side copy temp→final, a delete of the temp, an `initMeta` to stamp TTL metadata, and `.hash` / `.size` companion writes. Because the final key only appears after the copy, a crashed upload never leaves a corrupt committed entry. The committing save then enforces the per-org byte quota, evicting oldest entries until the org is back under `KICI_USER_CACHE_QUOTA_BYTES`.
|
|
10224
|
+
|
|
10225
|
+
### Trust → scope mapping
|
|
10226
|
+
|
|
10227
|
+
The orchestrator threads a `cacheRefScope` onto each `job.dispatch`. A **trusted** ref (the repo's own branches, default branch) maps to the `shared` write scope; any other ref (a fork PR) maps to `isolated`, writing to a per-run `iso/<runId>/` scope. This is the cache-isolation model: a fork can restore from the trusted `shared/` cache but can never write into it, so it cannot poison the entries a trusted branch later restores. The org segment of the key namespace (`cache/<orgId>/`) is the per-tenant boundary — no tenant can read another tenant's cache. See [orchestrator storage layout](../operator/orchestrator/storage-layout.md#user-cache) for the full prefix map and quota/TTL knobs.
|
|
10228
|
+
|
|
9414
10229
|
## Internal event routing flow
|
|
9415
10230
|
|
|
9416
10231
|
Internal events (custom events from `ctx.emit()` and system events from workflow/job completion) flow through the event router for fan-out delivery to matching workflows.
|
|
@@ -9443,11 +10258,11 @@ EventRouter.onNotification(eventId) [private]
|
|
|
9443
10258
|
| increments attempts and records claimed_at/claimed_by atomically)
|
|
9444
10259
|
|-- If lease acquired:
|
|
9445
10260
|
| |-- processSubscriptions(event):
|
|
9446
|
-
| | |-- If RegistrationIndex available
|
|
10261
|
+
| | |-- If RegistrationIndex available:
|
|
9447
10262
|
| | | Look up registrations by trigger type
|
|
9448
10263
|
| | | TrustStore.isTrusted() (for cross-repo events)
|
|
9449
10264
|
| | | matchAllWorkflows() against registered workflows
|
|
9450
|
-
| | |-- Else (
|
|
10265
|
+
| | |-- Else (no RegistrationIndex):
|
|
9451
10266
|
| | | TrustStore.isTrusted() (for cross-routing-key events)
|
|
9452
10267
|
| | | matchAllWorkflows() against in-memory lock file subscriptions
|
|
9453
10268
|
| | |-- For each match: onEventMatched(event, lockFile, matchedWorkflows)
|
|
@@ -9479,7 +10294,7 @@ Two invariants keep events from being silently lost:
|
|
|
9479
10294
|
event lands in the DLQ (`dlq_at` set, `dlq_reason='exhausted_retries'`)
|
|
9480
10295
|
and is surfaced via Prometheus (`kici_orch_event_dlq_*`), Grafana
|
|
9481
10296
|
(`event-delivery` dashboard), and the kici-admin CLI
|
|
9482
|
-
(`kici-admin event-dlq {list,retry,discard}`).
|
|
10297
|
+
(`kici-admin event-dlq {list,count,retry,discard}`).
|
|
9483
10298
|
- **Crash detection:** when a node crashes mid-dispatch, its lease ages out
|
|
9484
10299
|
after `leaseDurationMs` (default 60 s). The leader's
|
|
9485
10300
|
`EventRetryScanner` releases the expired lease and re-publishes
|
|
@@ -9915,144 +10730,9 @@ The Platform tier exposes a `/ws/browser` WebSocket endpoint for dashboard clien
|
|
|
9915
10730
|
|
|
9916
10731
|
---
|
|
9917
10732
|
|
|
9918
|
-
## Design decisions
|
|
9919
|
-
|
|
9920
|
-
Source: https://kici.dev/docs/architecture/design-decisions/
|
|
9921
|
-
|
|
9922
|
-
This page explains the rationale behind KiCI's major architectural choices. Each section describes the decision, why it was made, and what alternatives were considered.
|
|
9923
|
-
|
|
9924
|
-
## TypeScript over YAML
|
|
9925
|
-
|
|
9926
|
-
**Decision:** Workflows are defined in TypeScript, not YAML.
|
|
9927
|
-
|
|
9928
|
-
TypeScript provides full language power -- type checking, IDE autocomplete, refactoring tools, conditional logic, and composability. Workflows are regular TypeScript code that can be tested, imported, and reused like any other module. YAML-based CI systems (GitHub Actions, CircleCI) limit expressiveness to what the YAML schema supports, requiring workarounds for anything beyond basic conditionals.
|
|
9929
|
-
|
|
9930
|
-
**Alternative:** YAML workflow definitions (industry standard). Rejected because YAML constrains what users can express and makes workflows harder to test and refactor.
|
|
9931
|
-
|
|
9932
|
-
## Lock file approach
|
|
9933
|
-
|
|
9934
|
-
**Decision:** The compiler generates a `kici.lock.json` file that separates workflow definitions from runtime execution.
|
|
9935
|
-
|
|
9936
|
-
The lock file is a JSON snapshot of workflow definitions with static triggers pre-evaluated at compile time. This enables the orchestrator to match triggers without cloning code -- it fetches only the lock file via the GitHub API. Dynamic elements (dynamic jobs, dynamic matrices, rules) are represented as source references in the lock file and evaluated at runtime by the agent.
|
|
9937
|
-
|
|
9938
|
-
**Alternative:** Runtime evaluation of TypeScript at the routing tier. Rejected because it would require the orchestrator to clone repositories for trigger matching, adding latency and complexity to the dispatch path.
|
|
9939
|
-
|
|
9940
|
-
> See `packages/compiler/src/` for the compiler that generates lock files.
|
|
9941
|
-
|
|
9942
|
-
## Three-tier relay model
|
|
9943
|
-
|
|
9944
|
-
**Decision:** The system is split into three deployment tiers: Platform (webhook router), orchestrator (execution brain), and agent (execution worker).
|
|
9945
|
-
|
|
9946
|
-
This architecture separates trust boundaries. Customer code never leaves customer infrastructure -- the Platform tier handles only webhook verification and routing, with no access to customer code or secrets. The orchestrator matches triggers without cloning code. Only the agent, running on customer infrastructure, clones repositories and executes steps. The three-tier model also enables fully self-hosted deployment where all tiers run on customer infrastructure.
|
|
9947
|
-
|
|
9948
|
-
**Alternative:** Monolithic Platform that runs everything (customer code on KiCI servers). Rejected because it requires customers to trust a third party with their source code and secrets.
|
|
9949
|
-
|
|
9950
|
-
> See [Architecture Overview](overview.md) for the full three-tier diagram.
|
|
9951
|
-
|
|
9952
|
-
## Synchronous webhook relay
|
|
9953
|
-
|
|
9954
|
-
**Decision:** The Platform tier relays webhooks synchronously over WebSocket: verify signature, route to orchestrator, wait for ACK, respond to GitHub.
|
|
9955
|
-
|
|
9956
|
-
Synchronous relay is simpler, lower latency, and eliminates infrastructure dependencies. GitHub's built-in webhook retry mechanism handles the failure case -- if the Platform tier cannot relay (no orchestrator connected), it returns an error and GitHub retries automatically. This approach removed the need for a separate broker and an async job queue.
|
|
9957
|
-
|
|
9958
|
-
**Alternative:** Async queue (store webhook in a queue, process later). Rejected because it added infrastructure complexity (a separate broker and queue layer) without meaningful benefit -- GitHub already provides reliable retry behavior.
|
|
9959
|
-
|
|
9960
|
-
## Per-source webhook secrets
|
|
9961
|
-
|
|
9962
|
-
**Decision:** Each webhook source has its own webhook secret stored on the orchestrator side, dynamically registered by orchestrators via the `source.register` protocol message.
|
|
9963
|
-
|
|
9964
|
-
Per-source secrets enable multi-tenant Platform with proper isolation. Each customer's webhook source (e.g., a GitHub App) has a unique webhook secret, and signature verification uses the correct secret for each incoming webhook based on the routing key derived from the `X-GitHub-Hook-Installation-Target-ID` header.
|
|
9965
|
-
|
|
9966
|
-
**Alternative:** A single shared webhook secret via environment variable. Rejected because it cannot support multiple tenants and provides no isolation between customers.
|
|
9967
|
-
|
|
9968
|
-
## Routing key routing
|
|
9969
|
-
|
|
9970
|
-
**Decision:** Webhooks are routed by provider-scoped routing key (e.g., `github:12345`) derived from the `X-GitHub-Hook-Installation-Target-ID` header. One routing key maps to one orchestrator connection.
|
|
9971
|
-
|
|
9972
|
-
App ID routing provides a clean 1:1 mapping between a customer's GitHub App and their orchestrator. The orchestrator extracts the `installation_id` from the webhook payload body for GitHub API calls (fetching lock files, posting check runs).
|
|
9973
|
-
|
|
9974
|
-
**Alternative:** Route by Installation ID (more granular, one App can have many installations). Rejected because it adds complexity without clear benefit -- most customers use one App with one or a few installations, and the orchestrator can handle multiple installations internally.
|
|
9975
|
-
|
|
9976
|
-
## PostgreSQL as the orchestrator's only data store
|
|
9977
|
-
|
|
9978
|
-
**Decision:** The customer-deployable orchestrator depends on a single PostgreSQL database — no separate broker, cache, or pub/sub system. PostgreSQL is the primary data store on the Platform tier as well (the two tiers run independent databases).
|
|
9979
|
-
|
|
9980
|
-
PostgreSQL backs the orchestrator's durable job queue (delayed and recurring jobs, retries, dead-letter handling), workflow runs, dispatched jobs, sources, secrets, and event registrations. All DB access goes through a typed SQL query layer. Cross-instance coordination inside an orchestrator cluster (peer announcements, run cancellation, secret invalidation) uses PostgreSQL LISTEN/NOTIFY, so adding orchestrator replicas does not introduce a new infrastructure dependency.
|
|
9981
|
-
|
|
9982
|
-
**Alternative:** A separate broker / queue / pub-sub system alongside PostgreSQL. Rejected for the customer-deployable orchestrator because PostgreSQL covers durable jobs, transactional state changes, and cross-instance coordination in a single dependency — adding a broker would multiply operator surface area without buying customers a capability they don't already have.
|
|
9983
|
-
|
|
9984
|
-
## Pure-function state machine
|
|
9985
|
-
|
|
9986
|
-
**Decision:** The state machine uses pure functions: `transition(state, event) -> newState` with no internal state, no classes, and no mutation.
|
|
9987
|
-
|
|
9988
|
-
Pure functions are trivially testable (input/output, no setup), serializable (state is a string), and usable across all tiers without instantiation. The transition table is a plain `Record` for type-safe dispatch. The state machine tracks workflow runs, jobs, and steps using 11 states and 16 events.
|
|
9989
|
-
|
|
9990
|
-
**Alternative:** Class-based state machine with internal state (e.g., `machine.apply(event)`). Rejected because it is harder to test (requires instantiation and lifecycle management), harder to serialize (class instances), and adds unnecessary complexity for a simple state transition problem.
|
|
9991
|
-
|
|
9992
|
-
> See `packages/engine/src/state-machine/machine.ts` for the implementation and [State Machine](./execution/state-machine.md) for full documentation.
|
|
9993
|
-
|
|
9994
|
-
## Single agent binary with label-based routing
|
|
9995
|
-
|
|
9996
|
-
**Decision:** One agent binary, configured with labels via the `KICI_LABELS` environment variable (e.g., `linux,docker,gpu`). The orchestrator routes jobs by matching `runsOn` labels against registered agent labels.
|
|
9997
|
-
|
|
9998
|
-
A single binary simplifies deployment and maintenance. Labels provide flexible routing without requiring separate agent builds or deployments per platform. New capabilities are added by deploying the same binary with different labels and environment configuration.
|
|
9999
|
-
|
|
10000
|
-
**Alternative:** Separate agent binaries per platform (e.g., `kici-agent-linux`, `kici-agent-docker`). Rejected because it multiplies build and deployment complexity without meaningful benefit.
|
|
10001
|
-
|
|
10002
|
-
> See `packages/agent/src/config.ts` for label configuration.
|
|
10003
|
-
|
|
10004
|
-
## Lock file schema versioning
|
|
10005
|
-
|
|
10006
|
-
**Decision:** The lock file includes a `schemaVersion` field that tracks breaking changes to the trigger format.
|
|
10007
|
-
|
|
10008
|
-
| Version | Description |
|
|
10009
|
-
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
10010
|
-
| 12 | Current schema. Covers everything in v11 plus per-workflow `registries` and `installEnv` fields for private-package authentication. Token bytes never appear in the lock file — only qualified `<environment>:<secret-name>` references that the orchestrator resolves at dispatch time through the per-environment secret resolver. |
|
|
10011
|
-
| 11 | Covers the full trigger taxonomy (git events, internal events, schedule, lifecycle, generic webhooks), the `_type` discriminator on each trigger entry, polymorphic `runsOn` matching with `excludeLabels`, hook and concurrency flags on `LockJob`, repo-pattern matching with `!` negation, and `LockInlineValue` for pure-function inline evaluation. |
|
|
10012
|
-
|
|
10013
|
-
The `_type` field on each lock file trigger entry identifies the trigger type for the matcher's exhaustive switch dispatch. Users must recompile workflows with `pnpm kici compile` after upgrading to get the latest trigger types in their lock files.
|
|
10014
|
-
|
|
10015
|
-
**`SimulatedEvent.type`** is an open `string` type, allowing the `kici test` CLI to simulate any event type without requiring engine changes for each new event.
|
|
10016
|
-
|
|
10017
|
-
## Two-phase evaluation
|
|
10018
|
-
|
|
10019
|
-
**Decision:** Trigger matching happens in two phases: fast reject at the orchestrator (no clone needed) and full evaluation at the agent (clone and run TypeScript).
|
|
10020
|
-
|
|
10021
|
-
The orchestrator evaluates static triggers from the lock file -- branch patterns, path patterns, and event types. This is fast and requires only a small JSON file fetched via the GitHub API. The agent handles everything that requires code: rule evaluation (arbitrary TypeScript functions), dynamic job generation, and dynamic matrix expansion. This separation keeps the orchestrator fast and git-agnostic.
|
|
10022
|
-
|
|
10023
|
-
**Alternative:** Full evaluation at the orchestrator (clone repo, run TypeScript, then dispatch). Rejected because it would make the orchestrator slow, require git access at the routing tier, and blur the trust boundary between routing and execution.
|
|
10024
|
-
|
|
10025
|
-
## Registration model for event-triggered workflows
|
|
10026
|
-
|
|
10027
|
-
**Decision:** Workflows with non-Git triggers (schedule, kiciEvent, genericWebhook, workflowComplete, jobComplete, lifecycle) are extracted from the lock file on default-branch push and stored in a registration database. The orchestrator matches incoming events against these registrations without fetching the lock file each time.
|
|
10028
|
-
|
|
10029
|
-
**Why:** Git-based triggers (push, PR) work with per-event lock file fetching because the webhook itself provides the repo and ref. Internal events (cron fires, custom events, workflow completions) do not carry repo/ref information -- the orchestrator needs to know which workflows to evaluate before the event arrives. The registration model solves this by pre-populating the orchestrator's knowledge of event-triggered workflows.
|
|
10030
|
-
|
|
10031
|
-
**Alternative:** Fetch all lock files from all known repos on every internal event. Rejected because it would be O(repos \* events) API calls, adding unacceptable latency and GitHub API rate limit pressure.
|
|
10032
|
-
|
|
10033
|
-
> See `packages/orchestrator/src/registration/` for the implementation.
|
|
10034
|
-
|
|
10035
|
-
## Event trigger matching via registration index
|
|
10036
|
-
|
|
10037
|
-
**Decision:** The event router matches events exclusively against the DB-backed `RegistrationIndex`. A previous legacy in-memory subscription fallback was removed since the registration index is always available and is the sole authoritative source for trigger matching.
|
|
10038
|
-
|
|
10039
|
-
**Why:** A single code path eliminates drift risk between dual paths, halves the test surface, and avoids confusing future contributors about which path is authoritative. The project's pre-release "no backward compatibility" rule means deferred cleanup shouldn't accumulate.
|
|
10040
|
-
|
|
10041
|
-
> See `packages/orchestrator/src/events/event-router.ts` and `packages/orchestrator/src/registration/` for the implementation.
|
|
10042
|
-
|
|
10043
|
-
## See also
|
|
10044
|
-
|
|
10045
|
-
- [Architecture Overview](overview.md) -- the three-tier model and package structure
|
|
10046
|
-
- [State Machine](./execution/state-machine.md) -- details of the pure-function state machine
|
|
10047
|
-
- [Webhook Delivery](./webhooks/webhook-delivery.md) -- synchronous relay in action
|
|
10048
|
-
- [Reconnection](./clustering/reconnection.md) -- WebSocket resilience patterns
|
|
10049
|
-
- [Event System Internals](./webhooks/event-system.md) -- event router, registration model, cron scheduler
|
|
10050
|
-
|
|
10051
|
-
---
|
|
10052
|
-
|
|
10053
10733
|
## Architecture overview
|
|
10054
10734
|
|
|
10055
|
-
Source: https://kici.dev/
|
|
10735
|
+
Source: https://docs.kici.dev/architecture/overview/
|
|
10056
10736
|
|
|
10057
10737
|
KiCI uses a three-tier relay model that separates webhook routing from code execution. Customer code never leaves customer infrastructure -- the Platform tier handles only webhook verification and routing, while the orchestrator and agent tiers run on customer-managed servers.
|
|
10058
10738
|
|
|
@@ -10137,7 +10817,7 @@ Shared business logic used by all three tiers. Single source of truth for cross-
|
|
|
10137
10817
|
- Audit policy and retention (per-action access-log sampling, warm-retention windows for cold-store eligibility, federated activity row schema)
|
|
10138
10818
|
- Scaler backend type enum (`container`, `bare-metal`, `firecracker`, `kubernetes`)
|
|
10139
10819
|
- Registration trigger type enum (registerable trigger discriminator)
|
|
10140
|
-
- Bundler config (shared bundler configuration consumed by `e2e/helpers/service-deploy.ts`; the agent runtime uses the
|
|
10820
|
+
- Bundler config (shared bundler configuration consumed by `e2e/helpers/service-deploy.ts`; the agent runtime uses the `@kici-dev/core/ts-loader-hook` to transform TypeScript on import, with no runtime bundler step)
|
|
10141
10821
|
|
|
10142
10822
|
> Source: `packages/engine/src/`
|
|
10143
10823
|
|
|
@@ -10153,9 +10833,15 @@ CLI tooling for workflow authors. Compiles `.kici/workflows/*.ts` to `.kici/kici
|
|
|
10153
10833
|
|
|
10154
10834
|
> Source: `packages/compiler/src/`
|
|
10155
10835
|
|
|
10836
|
+
### `@kici-dev/core`
|
|
10837
|
+
|
|
10838
|
+
Light shared utilities with no server-side dependencies — JSON-structured logging, error helpers, human-readable formatting (`formatBytes`/`formatDuration`/`formatUptime`), cryptographic helpers (`sha256`/`sha256File`/`deriveSharedSecret`), zx initialization (`initZx()`), and the TypeScript loader hook that transforms TypeScript on import. It is the dependency-light core that the SDK, compiler, and `kici` CLI consume directly so they stay free of heavier server-only dependencies. `@kici-dev/shared` re-exports it, so existing `@kici-dev/shared` import paths keep working.
|
|
10839
|
+
|
|
10840
|
+
> Source: `packages/core/src/`
|
|
10841
|
+
|
|
10156
10842
|
### `@kici-dev/shared`
|
|
10157
10843
|
|
|
10158
|
-
Shared utilities used across packages. Provides `initZx()` for zx initialization, `createLogger()` for JSON-structured logging with TTY-aware formatting, `createPool()`/`createDb()` for typed PostgreSQL connections, `createMetricsRoutes()`/`createHealthRoutes()` for HTTP route factories (Prometheus metrics and health endpoints), `RingBuffer` for bounded collections, `requestContext`/`getRequestContext()`/`enrichRequestContext()` for async local storage request context, `getReconnectDelay()` for exponential backoff, `formatBytes`/`formatDuration`/`formatUptime` for human-readable formatting, `sha256`/`sha256File`/`deriveSharedSecret` for cryptographic utilities, `initTelemetry`/`createMeter` for OpenTelemetry integration, and `setupGracefulShutdown` for coordinated service shutdown with ordered steps.
|
|
10844
|
+
Shared utilities used across packages, including everything from `@kici-dev/core` (re-exported) plus server-side helpers. Provides `initZx()` for zx initialization, `createLogger()` for JSON-structured logging with TTY-aware formatting, `createPool()`/`createDb()` for typed PostgreSQL connections, `createMetricsRoutes()`/`createHealthRoutes()` for HTTP route factories (Prometheus metrics and health endpoints), `RingBuffer` for bounded collections, `requestContext`/`getRequestContext()`/`enrichRequestContext()` for async local storage request context, `getReconnectDelay()` for exponential backoff, `formatBytes`/`formatDuration`/`formatUptime` for human-readable formatting, `sha256`/`sha256File`/`deriveSharedSecret` for cryptographic utilities, `initTelemetry`/`createMeter` for OpenTelemetry integration, and `setupGracefulShutdown` for coordinated service shutdown with ordered steps.
|
|
10159
10845
|
|
|
10160
10846
|
> Source: `packages/shared/src/`
|
|
10161
10847
|
|
|
@@ -10181,6 +10867,7 @@ The following diagram shows how `@kici` packages depend on each other. Solid arr
|
|
|
10181
10867
|
|
|
10182
10868
|
```mermaid
|
|
10183
10869
|
flowchart TD
|
|
10870
|
+
CORE["@kici-dev/core"]
|
|
10184
10871
|
SDK["@kici-dev/sdk"]
|
|
10185
10872
|
COMPILER["@kici-dev/compiler"]
|
|
10186
10873
|
SHARED["@kici-dev/shared"]
|
|
@@ -10192,26 +10879,30 @@ flowchart TD
|
|
|
10192
10879
|
|
|
10193
10880
|
DASH --> ENGINE
|
|
10194
10881
|
DASH -.->|dev| PLATFORM
|
|
10882
|
+
SHARED --> CORE
|
|
10195
10883
|
SDK --> ENGINE
|
|
10196
|
-
SDK -->
|
|
10884
|
+
SDK --> CORE
|
|
10197
10885
|
COMPILER --> ENGINE
|
|
10198
|
-
COMPILER -->
|
|
10886
|
+
COMPILER --> CORE
|
|
10199
10887
|
COMPILER -.->|peer| SDK
|
|
10200
10888
|
PLATFORM --> ENGINE
|
|
10201
10889
|
PLATFORM --> SHARED
|
|
10202
10890
|
ORCH --> ENGINE
|
|
10203
10891
|
ORCH --> SHARED
|
|
10892
|
+
ORCH -.->|dev| AGENT
|
|
10204
10893
|
AGENT --> ENGINE
|
|
10205
10894
|
AGENT --> SDK
|
|
10206
10895
|
AGENT --> SHARED
|
|
10896
|
+
AGENT --> CORE
|
|
10207
10897
|
KICI["kici (wrapper)"]
|
|
10208
10898
|
KICI --> COMPILER
|
|
10209
|
-
KICI -->
|
|
10899
|
+
KICI --> CORE
|
|
10210
10900
|
KICIADMIN["kici-admin (admin CLI)"]
|
|
10211
10901
|
KICIADMIN --> ORCH
|
|
10902
|
+
KICIADMIN --> AGENT
|
|
10212
10903
|
```
|
|
10213
10904
|
|
|
10214
|
-
**Leaf packages** (no `@kici` dependencies): `@kici-dev/
|
|
10905
|
+
**Leaf packages** (no `@kici` dependencies): `@kici-dev/core` and `@kici-dev/engine`. These can be tested and built independently. `@kici-dev/shared` builds on `@kici-dev/core` and re-exports it. The dashboard depends on `@kici-dev/engine` for shared types (protocol schemas, state machine) and imports the Platform's API type definitions as a dev dependency, but communicates with backend services at runtime via HTTP/WebSocket, not at compile time.
|
|
10215
10906
|
|
|
10216
10907
|
**Runtime tiers** (Platform, orchestrator, agent) all depend on `@kici-dev/engine` for shared business logic and `@kici-dev/shared` for utilities. Only the agent depends on `@kici-dev/sdk` (it loads workflow definitions at runtime).
|
|
10217
10908
|
|
|
@@ -10245,6 +10936,5 @@ KiCI uses application-level tenant isolation. The Platform dashboard API accepts
|
|
|
10245
10936
|
- [State Machine](./execution/state-machine.md) -- execution lifecycle tracking across all tiers
|
|
10246
10937
|
- [Protocol Messages](protocol-messages.md) -- WebSocket message schemas for all three layers
|
|
10247
10938
|
- [Webhook Delivery](./webhooks/webhook-delivery.md) -- end-to-end trace of a webhook through all three tiers
|
|
10248
|
-
- [Design Decisions](design-decisions.md) -- rationale behind the three-tier model and other architectural choices
|
|
10249
10939
|
|
|
10250
10940
|
---
|