@kici-dev/compiler 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +10 -2
- package/dist/commands/compile.js +5 -1
- package/dist/commands/doctor.js +8 -2
- package/dist/commands/feedback.d.ts +53 -0
- package/dist/commands/feedback.js +142 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +2 -1
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +77 -12
- package/dist/commands/preview.js +1 -1
- package/dist/commands/report/identity.d.ts +11 -0
- package/dist/commands/report/identity.js +7 -2
- package/dist/commands/run-routed.js +1 -0
- package/dist/commands/types.d.ts +6 -1
- package/dist/commands/types.js +2 -1
- package/dist/execution/executor.js +7 -1
- package/dist/llm-context/llms-architecture.txt +72 -86
- package/dist/llm-context/llms-cli-remote.txt +2380 -0
- package/dist/llm-context/llms-cli.txt +348 -2615
- package/dist/llm-context/llms-features-execution.txt +80 -23
- package/dist/llm-context/llms-features.txt +137 -6
- package/dist/llm-context/llms-full.txt +2582 -2025
- package/dist/llm-context/llms-getting-started.txt +152 -5
- package/dist/llm-context/llms-patterns.txt +81 -5
- package/dist/llm-context/llms-providers.txt +6 -2
- package/dist/llm-context/llms-sdk-runtime.txt +22 -18
- package/dist/llm-context/llms-sdk.txt +47 -7
- package/dist/llm-context/llms.txt +20 -13
- package/dist/local-plane/orchestrator-process.d.ts +0 -8
- package/dist/local-plane/orchestrator-process.js +3 -14
- package/dist/local-plane/plane-manager.js +2 -2
- package/dist/lockfile/generator.js +25 -9
- package/dist/lockfile/hasher.d.ts +5 -13
- package/dist/lockfile/hasher.js +1 -15
- package/dist/lockfile/workspace-siblings.d.ts +46 -0
- package/dist/lockfile/workspace-siblings.js +197 -0
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/job-executor.js +1 -1
- package/dist/test-runner/rule-evaluator.js +1 -1
- package/dist/types.d.ts +6 -1
- package/package.json +7 -9
- package/sbom.spdx.json +123 -123
- package/dist/postinstall.d.ts +0 -9
- package/dist/postinstall.js +0 -62
- package/hack/postinstall.mjs +0 -105
|
@@ -132,6 +132,8 @@ workflow('test', {
|
|
|
132
132
|
|
|
133
133
|
When `max: 1` (default), runs are fully serialized within the group.
|
|
134
134
|
|
|
135
|
+
`max` is enforced by the orchestrator's database, so the limit is cluster-wide and survives a restart. A run holding a slot keeps it across an orchestrator restart. Every orchestrator in a cluster counts against the same limit, so a group with `max: 1` runs one job at a time no matter which orchestrator dispatched it.
|
|
136
|
+
|
|
135
137
|
## Group key examples
|
|
136
138
|
|
|
137
139
|
### Deploy per environment
|
|
@@ -191,6 +193,14 @@ When a workflow has both `concurrency` and `context` protection rules:
|
|
|
191
193
|
|
|
192
194
|
This means a run that passed approval won't need re-approval if it gets queued by concurrency.
|
|
193
195
|
|
|
196
|
+
The two caps also differ in strength. A workflow-level concurrency group claims
|
|
197
|
+
its slot inside a single database transaction, so two runs that arrive together
|
|
198
|
+
cannot both take it. A context
|
|
199
|
+
[concurrency limit](https://docs.kici.dev/user/contexts/#concurrency-limits) is a throughput control:
|
|
200
|
+
simultaneous arrivals can each be admitted before either is recorded. Declare a
|
|
201
|
+
concurrency group for anything that must never run beside itself, whatever the
|
|
202
|
+
context limit says.
|
|
203
|
+
|
|
194
204
|
## Cancelling queued runs
|
|
195
205
|
|
|
196
206
|
Queued runs can be cancelled before they start executing. The cancel request removes them from the queue immediately -- they don't go through the grace period since no step is running.
|
|
@@ -401,6 +411,17 @@ Store the secrets first with `kici-admin secret set`. Pasting a token straight
|
|
|
401
411
|
into the workflow is rejected when the workflow is defined, because a token
|
|
402
412
|
written into `.kici/` would be committed to your repository.
|
|
403
413
|
|
|
414
|
+
The named context's protection rules run before the secret is read, exactly as
|
|
415
|
+
they do for [git credentials](https://docs.kici.dev/user/patterns/git-credentials/#what-a-job-may-ask-for).
|
|
416
|
+
A `prod:` reference from a branch the `prod` context restricts is refused, and
|
|
417
|
+
the job is dispatched with no registry credentials — so a private image fails to
|
|
418
|
+
pull rather than being pulled from a branch the context does not allow. The rule
|
|
419
|
+
that refused it is named in your orchestrator's log, not in the run.
|
|
420
|
+
|
|
421
|
+
**An untrusted ref receives no registry credentials.** A fork pull request is
|
|
422
|
+
dispatched without them, so a private base image fails to pull and a public one
|
|
423
|
+
is unaffected. The run's reduced-privilege note says so.
|
|
424
|
+
|
|
404
425
|
The username is not a secret, so you may write it directly:
|
|
405
426
|
|
|
406
427
|
```typescript
|
|
@@ -410,6 +431,30 @@ auth: { username: 'ci-bot', tokenSecret: 'prod:REGISTRY_TOKEN' }
|
|
|
410
431
|
Your orchestrator resolves these names at dispatch and sends only the resolved
|
|
411
432
|
credentials to the agent. The agent never reads your secret store.
|
|
412
433
|
|
|
434
|
+
### Naming the registry
|
|
435
|
+
|
|
436
|
+
`auth` also takes a `registry` — the registry host the credentials belong to,
|
|
437
|
+
such as `reg.internal:5000`.
|
|
438
|
+
|
|
439
|
+
With `image` it is optional, because KiCI reads the host off the image
|
|
440
|
+
reference. With `dockerfile` it is **required**: the base image is named inside
|
|
441
|
+
your Dockerfile, so there is nothing to read it from. A `dockerfile` job whose
|
|
442
|
+
`auth` omits `registry` is refused when you define the workflow.
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
container: {
|
|
446
|
+
dockerfile: '.kici/ci.Dockerfile',
|
|
447
|
+
auth: {
|
|
448
|
+
registry: 'reg.internal:5000',
|
|
449
|
+
usernameSecret: 'prod:REGISTRY_USER',
|
|
450
|
+
tokenSecret: 'prod:REGISTRY_TOKEN',
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
With `dockerfile`, these credentials pull the Dockerfile's own `FROM` base — not
|
|
456
|
+
a job image, since the job image is the one KiCI builds.
|
|
457
|
+
|
|
413
458
|
### Credentials that only exist at run time
|
|
414
459
|
|
|
415
460
|
A token fetched during the run — from a cloud registry's login command, for
|
|
@@ -560,10 +605,11 @@ The KiCI CLI reads the following environment variables to customize its behavior
|
|
|
560
605
|
|
|
561
606
|
## Development
|
|
562
607
|
|
|
563
|
-
| Variable
|
|
564
|
-
|
|
|
565
|
-
| `KICI_DEV`
|
|
566
|
-
| `
|
|
608
|
+
| Variable | Description | Default |
|
|
609
|
+
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
|
610
|
+
| `KICI_DEV` | Enable development mode. When `true`, uses prerelease-compatible version ranges (`>=0.0.1-0`) for dev dependencies and skips npm version resolution. | unset |
|
|
611
|
+
| `KICI_DEV_REGISTRY` | npm registry the `@kici-dev` scope points at when `KICI_DEV` is set. `kici init` writes it into `.npmrc`; with no value it writes no `.npmrc`. | unset |
|
|
612
|
+
| `KICI_DEBUG` | Enable debug logging. When `true`, prints verbose diagnostics (SDK alias resolution, step-level debug logs, stack traces on errors). Equivalent to the `--debug` CLI flag. | unset |
|
|
567
613
|
|
|
568
614
|
## Local dev plane
|
|
569
615
|
|
|
@@ -897,7 +943,7 @@ job('provision', {
|
|
|
897
943
|
|
|
898
944
|
### Trust tiers on internal triggers
|
|
899
945
|
|
|
900
|
-
An internally-triggered run resolves its [trust tier](https://docs.kici.dev/user/contexts/#minimum-trust) from the trigger. The tier decides the run's cache scope, whether it may run a [Dockerfile build](https://docs.kici.dev/user/container-jobs/#who-may-build), whether it receives [install secrets](https://docs.kici.dev/user/private-registries/), and whether a `minimumTrust` context holds it.
|
|
946
|
+
An internally-triggered run resolves its [trust tier](https://docs.kici.dev/user/contexts/#minimum-trust) from the trigger. The tier decides the run's cache scope, whether it may run a [Dockerfile build](https://docs.kici.dev/user/container-jobs/#who-may-build), whether it receives [install secrets](https://docs.kici.dev/user/private-registries/), and whether a `minimumTrust` context holds it. It also decides whether the run's jobs receive [container-registry credentials](https://docs.kici.dev/user/container-jobs/#private-images) and their declared [git credentials](https://docs.kici.dev/user/patterns/git-credentials/#what-a-job-may-ask-for).
|
|
901
947
|
|
|
902
948
|
Four rules resolve the tier, and KiCI applies them in this order:
|
|
903
949
|
|
|
@@ -1153,14 +1199,21 @@ Patterns in `repos:` use the same globbing as `branches:` / `paths:` — plain g
|
|
|
1153
1199
|
|
|
1154
1200
|
The agent checks out both repos. **Inside a step body**, `env` carries a pointer to each working tree:
|
|
1155
1201
|
|
|
1156
|
-
| `env` var |
|
|
1202
|
+
| `env` var | Carries |
|
|
1157
1203
|
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1158
1204
|
| `KICI_SOURCE_REPO_PATH` | The **source** repo's working tree (the repo that emitted the event). This is the repo the job's `$` / `git` commands operate on by default. |
|
|
1159
1205
|
| `KICI_WORKFLOW_REPO_PATH` | The **workflow** repo's working tree (the repo that authored the workflow). Useful for reading shared scripts or config from your CI repo. |
|
|
1206
|
+
| `KICI_SOURCE_REPO` | The source repo's `owner/repo` identifier — the same value as `ctx.event.sourceRepo`. |
|
|
1207
|
+
| `KICI_WORKFLOW_REPO` | The workflow repo's `owner/repo` identifier. |
|
|
1208
|
+
| `KICI_SOURCE_BRANCH` | The source repo's checked-out ref. **Empty string** when the event carries no single ref. |
|
|
1209
|
+
| `KICI_SOURCE_SHA` | The source repo's checked-out commit. **Empty string** when the event carries no single sha. |
|
|
1210
|
+
| `KICI_IS_GLOBAL_WORKFLOW` | `"true"`. Never set on the same-repo path, so it is the cheapest test for which path you are on. |
|
|
1211
|
+
|
|
1212
|
+
All seven are set **only when there are two repos to point at**. An event from the workflow's own repo is matched from that repo's lock file, not as a global candidate. The workflow then runs as an ordinary single-repo workflow: one checkout, and none of the seven set. Read them with a fallback, as the example above does.
|
|
1160
1213
|
|
|
1161
|
-
|
|
1214
|
+
Guard those two on emptiness rather than absence: `??` does not catch `""`, but `||` does.
|
|
1162
1215
|
|
|
1163
|
-
|
|
1216
|
+
These are real process environment variables for the whole job, so a subprocess a step spawns inherits them: `` await $`echo $KICI_SOURCE_REPO` `` works. What does **not** see them is anything resolved outside that process — a job-level `env:` block or a container image's entrypoint, both of which are settled before the job starts. Outside a step body, use the `sourceRepo` / `workflowRepo` pair on the filter, generator, and rule contexts described below.
|
|
1164
1217
|
|
|
1165
1218
|
A global workflow's job runs with **no secrets at all** — neither the source repo's nor its own. See _Secrets are not available_ below.
|
|
1166
1219
|
|
|
@@ -1409,11 +1462,15 @@ A global workflow fires only if:
|
|
|
1409
1462
|
|
|
1410
1463
|
Both checks are logged to the orchestrator. Grep for `Global workflows excluded from registration` (registration time) and `Skipping global workflow dispatch` (dispatch time) to see enforcement in action.
|
|
1411
1464
|
|
|
1412
|
-
Both checks read the settings of the organization the **event's source** resolves to. If no webhook source maps the event's routing key to an organization, the orchestrator resolves the built-in `__default__` organization anchor instead
|
|
1465
|
+
Both checks read the settings of the organization the **event's source** resolves to. If no webhook source maps the event's routing key to an organization, the orchestrator resolves the built-in `__default__` organization anchor instead. That anchor is an ordinary organization for policy purposes: it carries no per-org lists, so by the empty-list rule above it restricts nothing, and the fleet-wide master switch alone governs it. A deployment whose sources are unmapped — the state a fresh install starts in, since the quickstart configures no sources — runs global workflows normally once that switch is on.
|
|
1466
|
+
|
|
1467
|
+
Map a source to a real organization when you want per-org policy to be **expressible**: `kici-admin source update <routingKey> --customer-id <org>`. Allow- and deny-lists are stored per organization, so every unmapped source shares the one policy surface on the `__default__` anchor. That is a reason to map, not a precondition for dispatch.
|
|
1468
|
+
|
|
1469
|
+
The registration log line names the organization it decided against, so a refusal is always attributable to a specific policy rather than to the anchor itself. See the troubleshooting table below.
|
|
1413
1470
|
|
|
1414
1471
|
### Secrets are not available
|
|
1415
1472
|
|
|
1416
|
-
A global workflow's job is dispatched with **no secret material** — not the source repo's, and not the workflow repo's own. The organization-wide dispatch path binds no secret contexts, so a `contexts:` declaration on a global workflow resolves to nothing and any secret the steps expect is
|
|
1473
|
+
A global workflow's job is dispatched with **no secret material** — not the source repo's, and not the workflow repo's own. The organization-wide dispatch path binds no secret contexts, so a `contexts:` declaration on a global workflow resolves to nothing and any secret the steps expect is absent. Plan for it: a global workflow is for checks, policy and reporting that need only the two checkouts, not for deploys that need credentials.
|
|
1417
1474
|
|
|
1418
1475
|
This is about your **stored secrets**, not about repository access: the job is still handed a short-lived clone token for each repo it checks out, which is how the dual checkout works at all. What it does not get is anything from a secret context.
|
|
1419
1476
|
|
|
@@ -1468,19 +1525,19 @@ A `filter` reads the source tree, so the evaluation must be able to obtain one.
|
|
|
1468
1525
|
|
|
1469
1526
|
## Troubleshooting
|
|
1470
1527
|
|
|
1471
|
-
| Symptom | Likely cause | Where to look
|
|
1472
|
-
| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1473
|
-
| Global workflow registered but never runs | Master toggle OFF, or allow-list blocks the authoring repo, or deny-list blocks the source repo | Orchestrator log: `Skipping global workflow dispatch` (dispatch time) / `Global workflows excluded from registration` (registration time)
|
|
1474
|
-
| A global workflow is never registered at all — it is absent from `kici-admin registration list` | The
|
|
1475
|
-
| `repos:` has no effect — workflow only fires on its own repo | The fleet-wide master switch is off. Without it, the orchestrator treats the workflow as per-repo-only. | Check the fleet-wide switch with `kici-admin cluster-settings show`. The dashboard → Settings → Global workflows tab shows it as a read-only badge.
|
|
1476
|
-
| Secrets unavailable in a global job | Expected — a global workflow's job receives no secrets at all, and the _Elevated access_ list is not enforced. | Move the jobs that need credentials into a per-repository workflow in the repo that owns the secrets
|
|
1477
|
-
| Dashboard shows workflow twice after registering | Both a generic webhook source and a provider source (github, generic) re-registered the same repo. | Check `workflow_registrations` via `kici-admin workflow list` and confirm the right routing key owns the workflow.
|
|
1478
|
-
| Global workflow registered, enabled, allowed — and still no run appears | Its `filter` returned `false`. A global filter runs before the run is created, so a suppressed workflow leaves nothing behind at all. | [Reading a global workflow's filter output](https://docs.kici.dev/user/global-workflows/#reading-a-global-workflows-filter-output) — the evaluation round's own log. The orchestrator also logs `Global workflow skipped by eval round`, naming the workflow and the reason.
|
|
1479
|
-
| Global workflow never fires for one particular source repo | Its `repos:` patterns do not match that repo's identifier. | Orchestrator log: `Global workflows dropped by their repos filter` — one line per delivery, naming each dropped workflow, its repo and its patterns.
|
|
1480
|
-
| A `failure` check named `KiCI: Organization workflow evaluation` on a commit | The pre-run evaluation failed or timed out, so the global workflows from that repo were not run. | Orchestrator log for the evaluation job. Fix the cause, then re-run the errored `__globaleval__…` run (`kici runs rerun <run-id>`) to re-evaluate and clear the check; a redelivery is dropped as a duplicate.
|
|
1481
|
-
| Same-repo workflow shows a `success` run with no jobs in it | Its `filter` returned `false`. A same-repo filter runs after the run exists, so the run remains, carrying only the evaluation jobs. | The run detail page — the evaluation job's log records the filter verdict.
|
|
1482
|
-
| Re-run is refused with "Cannot re-run an organization-wide workflow" | Expected — the run executed against a source repo that does not declare the workflow. | [Re-running an organization-wide run](https://docs.kici.dev/user/global-workflows/#re-running-an-organization-wide-run) — trigger it from the repo that defines the workflow instead.
|
|
1483
|
-
| Every global workflow stopped running right after an orchestrator upgrade | The agents were not upgraded first. An agent older than v0.5.0 cannot evaluate a global workflow, and one containing a `dynamicJob` now needs an evaluation even without a `filter` — so its **static** jobs stop too. | The `KiCI: Organization workflow evaluation` check names the agent versions it found. Upgrade every `kici:role:init-runner` agent to v0.5.0 or newer.
|
|
1528
|
+
| Symptom | Likely cause | Where to look |
|
|
1529
|
+
| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1530
|
+
| Global workflow registered but never runs | Master toggle OFF, or allow-list blocks the authoring repo, or deny-list blocks the source repo | Orchestrator log: `Skipping global workflow dispatch` (dispatch time) / `Global workflows excluded from registration` (registration time) |
|
|
1531
|
+
| A global workflow is never registered at all — it is absent from `kici-admin registration list` | The fleet-wide master switch is off, or the authoring repo does not match a populated _Allowed author repos_ list. | Orchestrator log: `Global workflows excluded from registration`, naming the organization it decided against. Check the switch first (`kici-admin cluster-settings show`), then that org's allow-list in the dashboard. An `"orgId": "__default__"` in the line is not itself the fault — that anchor carries no lists and restricts nothing. |
|
|
1532
|
+
| `repos:` has no effect — workflow only fires on its own repo | The fleet-wide master switch is off. Without it, the orchestrator treats the workflow as per-repo-only. | Check the fleet-wide switch with `kici-admin cluster-settings show`. The dashboard → Settings → Global workflows tab shows it as a read-only badge. |
|
|
1533
|
+
| Secrets unavailable in a global job | Expected — a global workflow's job receives no secrets at all, and the _Elevated access_ list is not enforced. | Move the jobs that need credentials into a per-repository workflow in the repo that owns the secrets |
|
|
1534
|
+
| Dashboard shows workflow twice after registering | Both a generic webhook source and a provider source (github, generic) re-registered the same repo. | Check `workflow_registrations` via `kici-admin workflow list` and confirm the right routing key owns the workflow. |
|
|
1535
|
+
| Global workflow registered, enabled, allowed — and still no run appears | Its `filter` returned `false`. A global filter runs before the run is created, so a suppressed workflow leaves nothing behind at all. | [Reading a global workflow's filter output](https://docs.kici.dev/user/global-workflows/#reading-a-global-workflows-filter-output) — the evaluation round's own log. The orchestrator also logs `Global workflow skipped by eval round`, naming the workflow and the reason. |
|
|
1536
|
+
| Global workflow never fires for one particular source repo | Its `repos:` patterns do not match that repo's identifier. | Orchestrator log: `Global workflows dropped by their repos filter` — one line per delivery, naming each dropped workflow, its repo and its patterns. |
|
|
1537
|
+
| A `failure` check named `KiCI: Organization workflow evaluation` on a commit | The pre-run evaluation failed or timed out, so the global workflows from that repo were not run. | Orchestrator log for the evaluation job. Fix the cause, then re-run the errored `__globaleval__…` run (`kici runs rerun <run-id>`) to re-evaluate and clear the check; a redelivery is dropped as a duplicate. |
|
|
1538
|
+
| Same-repo workflow shows a `success` run with no jobs in it | Its `filter` returned `false`. A same-repo filter runs after the run exists, so the run remains, carrying only the evaluation jobs. | The run detail page — the evaluation job's log records the filter verdict. |
|
|
1539
|
+
| Re-run is refused with "Cannot re-run an organization-wide workflow" | Expected — the run executed against a source repo that does not declare the workflow. | [Re-running an organization-wide run](https://docs.kici.dev/user/global-workflows/#re-running-an-organization-wide-run) — trigger it from the repo that defines the workflow instead. |
|
|
1540
|
+
| Every global workflow stopped running right after an orchestrator upgrade | The agents were not upgraded first. An agent older than v0.5.0 cannot evaluate a global workflow, and one containing a `dynamicJob` now needs an evaluation even without a `filter` — so its **static** jobs stop too. | The `KiCI: Organization workflow evaluation` check names the agent versions it found. Upgrade every `kici:role:init-runner` agent to v0.5.0 or newer. |
|
|
1484
1541
|
|
|
1485
1542
|
### Reading the decision trace for a delivery
|
|
1486
1543
|
|
|
@@ -66,7 +66,7 @@ You declare a gate in your workflow with `approval`. It is available at three le
|
|
|
66
66
|
- **Job** — hold the job before any of its steps run.
|
|
67
67
|
- **Workflow** — hold the whole run before any job is dispatched.
|
|
68
68
|
|
|
69
|
-
A step-level gate can also fire **only when a check/apply step finds drift** — Terraform's plan→apply, per step. See [Drift gates](https://docs.kici.dev/user/approvals/#drift-gates-
|
|
69
|
+
A step-level gate can also fire **only when a check/apply step finds drift** — Terraform's plan→apply, per step. See [Drift gates](https://docs.kici.dev/user/approvals/#drift-gates-when-drift) below.
|
|
70
70
|
|
|
71
71
|
Approvers are named as **teams** and **users**. A team is an operator-defined group of org members; your workflow code may name a team but can never change its membership, which is what makes a team clause a real gate rather than a suggestion. See [Approval gates (operator guide)](https://docs.kici.dev/operator/approvals/) for how operators define teams, the approval queue, and expiry; see [the architecture overview](https://docs.kici.dev/architecture/approvals/) for how a hold is evaluated and resumed.
|
|
72
72
|
|
|
@@ -137,7 +137,7 @@ approval: {
|
|
|
137
137
|
|
|
138
138
|
| Field | Type | Description |
|
|
139
139
|
| ----------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
-
| `when` | `'always' \| 'drift'` | When the gate fires. `'always'` (default) gates before the element; `'drift'` gates a check/apply step only when it finds drift. See [Drift gates](https://docs.kici.dev/user/approvals/#drift-gates-
|
|
140
|
+
| `when` | `'always' \| 'drift'` | When the gate fires. `'always'` (default) gates before the element; `'drift'` gates a check/apply step only when it finds drift. See [Drift gates](https://docs.kici.dev/user/approvals/#drift-gates-when-drift). |
|
|
141
141
|
| `approvers` | `ApproverClause[]` | The AND list of `{ team }` / `{ user }` clauses. An empty list means "any approval-capable member". |
|
|
142
142
|
| `reason` | `string` | A human-readable label shown in the dashboard queue and the held-for-approval status check. |
|
|
143
143
|
| `timeout` | `number` | Per-gate expiry in **seconds**, overriding the org default. Must be a **positive integer** number of seconds; a non-positive or non-finite value is rejected at compile time. On expiry the element is rejected. |
|
|
@@ -316,7 +316,7 @@ Available rules:
|
|
|
316
316
|
- **Branch restrictions** — only allow specific branches to deploy.
|
|
317
317
|
- **Required reviewer approvals** — gate the run on human sign-off.
|
|
318
318
|
- **Wait timers** — delay execution for a fixed period.
|
|
319
|
-
- **Concurrency limits** —
|
|
319
|
+
- **Concurrency limits** — cap how many jobs run against the context at once.
|
|
320
320
|
|
|
321
321
|
<!-- /help:contexts-protection -->
|
|
322
322
|
|
|
@@ -405,7 +405,7 @@ job('deploy', {
|
|
|
405
405
|
|
|
406
406
|
**Skip-on-test (allow-and-warn).** On a test or local run (`kici run remote`, `kici run <event> --local`), a bound context never rejects the run. Any bound context that disallows local execution (`allowLocalExecution: false`) — or that is not configured — is **skipped**: its variables and secrets are omitted from the merge and its gates are not evaluated. The run proceeds, and a user-visible warning naming the skipped context(s) is shown both on the `kici run remote` CLI output and on the dashboard run view. This makes the test-only-variables pattern work: with `contexts: ['staging', 'my-testing']` where only `my-testing` allows local execution, a test run resolves just `my-testing`'s variables and warns that `staging` was skipped. If every bound context is skipped, the job runs with no environment variables. This is intentionally different from a fixture `secrets:` mapping, which is fail-closed — see the [testing guide](https://docs.kici.dev/user/testing-guide/).
|
|
407
407
|
|
|
408
|
-
**Unconfigured contexts contribute nothing at dispatch.** At dispatch time a bound context name with no matching configured context (and no matching glob context)
|
|
408
|
+
**Unconfigured contexts contribute nothing at dispatch.** At dispatch time a bound context name with no matching configured context (and no matching glob context) adds no variables, secrets, or protection rules — the job still runs, exactly as a single dynamic context resolving to an as-yet-unconfigured name does today.
|
|
409
409
|
|
|
410
410
|
**Registration rejects a provably-unsatisfiable binding.** When a workflow is registered, KiCI statically checks every multi-context binding: a bound context that does not exist, a disabled one, or two contexts with mutually-exclusive fixed branch / trigger-type / repository restrictions (no value can satisfy both) makes the binding provably unsatisfiable, and the registration is rejected with a precise message naming the job, the contexts, and the rule — for example `unsatisfiable context binding: job 'deploy' binds contexts [staging, my-testing] with mutually exclusive branch restrictions (no value satisfies all bound contexts)`. Bindings whose restrictions use globs are undecidable at registration and fall through to the dispatch-time gate check instead.
|
|
411
411
|
|
|
@@ -608,6 +608,36 @@ The children of a matrix job count individually against the limit. A three-child
|
|
|
608
608
|
matrix bound to a context with a limit of two dispatches two children and applies
|
|
609
609
|
the strategy above to the third.
|
|
610
610
|
|
|
611
|
+
A job counts against the limit from the moment it is dispatched until it
|
|
612
|
+
finishes. It does not have to reach an agent first.
|
|
613
|
+
|
|
614
|
+
#### What the limit does and does not guarantee
|
|
615
|
+
|
|
616
|
+
A context concurrency limit is a **throughput control**. Treat it as a cap on
|
|
617
|
+
how much work runs at once, not as a lock.
|
|
618
|
+
|
|
619
|
+
Two events that arrive in the same instant read the limit before either job is
|
|
620
|
+
recorded, so each can be admitted. The window is short — the time between one
|
|
621
|
+
read and one write — but it is real, and it grows with the number of
|
|
622
|
+
orchestrator processes serving the context.
|
|
623
|
+
|
|
624
|
+
When a job must never run beside another copy of itself — a production deploy,
|
|
625
|
+
a database migration — declare a workflow-level
|
|
626
|
+
[concurrency group](https://docs.kici.dev/user/concurrency/) as well:
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
export default workflow('deploy', {
|
|
630
|
+
on: push({ branches: ['main'] }),
|
|
631
|
+
concurrency: { group: () => 'deploy-prod', max: 1 },
|
|
632
|
+
jobs: [/* ... */],
|
|
633
|
+
});
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
That mechanism claims its slot inside a single database transaction, so two
|
|
637
|
+
runs arriving together cannot both take it. The two are complementary: the
|
|
638
|
+
context limit caps throughput across every workflow bound to the context, and
|
|
639
|
+
the concurrency group serializes one workflow against itself.
|
|
640
|
+
|
|
611
641
|
## Dashboard management
|
|
612
642
|
|
|
613
643
|
### Creating contexts
|
|
@@ -914,7 +944,7 @@ registries: [
|
|
|
914
944
|
## Security model
|
|
915
945
|
|
|
916
946
|
- **Per-context scoping.** Every `tokenSecret` and `installEnv` entry is qualified with a context name. The orchestrator runs the same protection-rule pipeline (branch / trust / concurrency / reviewer / wait-timer) against each named context **before** resolving any secret, so a workflow that wants a `production` token from a feature branch is rejected exactly like a job that tries to deploy to `production` from a feature branch. A reviewer-gated install context **pauses** the whole workflow dispatch as a workflow-scoped held run instead of resolving the token — see [Reviewer-gated installs](https://docs.kici.dev/user/private-registries/#reviewer-gated-installs) below.
|
|
917
|
-
- **Untrusted refs get no tokens.** When the trust resolution returns anything other than `trusted` — every fork pull request does — the orchestrator strips
|
|
947
|
+
- **Untrusted refs get no tokens.** When the trust resolution returns anything other than `trusted` — every fork pull request does — the orchestrator strips `npmRegistries`, `installEnvSecrets`, and a container job's [registry credentials](https://docs.kici.dev/user/container-jobs/#private-images) out of the dispatch. The install runs without auth and fails naturally on the first private dep, and a private base image fails to pull. A fork pull request cannot observe a registry token, even if a context lacks an explicit [minimum trust](https://docs.kici.dev/user/contexts/#minimum-trust) rule.
|
|
918
948
|
- **Lifecycle scripts disabled.** Whenever a private registry is in scope, the agent runs the install with `--ignore-scripts` (npm, pnpm, and yarn classic alike; yarn berry gets the equivalent `enableScripts: false`). A malicious `preinstall` / `postinstall` hook in committed `package.json` cannot read the synthesized token env vars, even though they exist in the install subprocess. For a pnpm or yarn workspace, the agent builds your in-repo dependency closure as a separate step **after** the install's auth is torn down, so build scripts never see the tokens either.
|
|
919
949
|
- **Stderr is redacted.** If the install fails, the agent masks every token literal out of the surfaced stderr / stdout chunks before logging.
|
|
920
950
|
- **Job-scoped env-var names.** The synthesized auth env var is `KICI_NPM_TOKEN_<jobIdShort>_<i>` where `jobIdShort` is the first 8 chars of the dispatched job id. The name is unguessable from outside the install subprocess and not reused across jobs.
|
|
@@ -950,7 +980,7 @@ The dashboard JSON lives at `infra/terraform/modules/grafana/dashboards/install-
|
|
|
950
980
|
## See also
|
|
951
981
|
|
|
952
982
|
- [Secrets](https://docs.kici.dev/user/secrets/) — how to seed the `<context>:<secret-name>` values referenced by `tokenSecret` / `installEnv`.
|
|
953
|
-
- [Contexts](https://docs.kici.dev/user/contexts/) — protection rules (
|
|
983
|
+
- [Contexts](https://docs.kici.dev/user/contexts/) — protection rules (branch restrictions, required reviewers, minimum trust) that the install gate inherits.
|
|
954
984
|
- [Operator: `kici-admin org-settings`](https://docs.kici.dev/operator/orchestrator/kici-admin/org-settings/#org-settings----org-level-security-policy) — the `allow_http_npm_registries` toggle and other org-scoped knobs.
|
|
955
985
|
|
|
956
986
|
---
|
|
@@ -1068,6 +1098,107 @@ and the step never holds signing credentials — the orchestrator mints and sign
|
|
|
1068
1098
|
the token on the step's behalf from its own run records. Like `attestProvenance`,
|
|
1069
1099
|
it is only available inside a running job step.
|
|
1070
1100
|
|
|
1101
|
+
## ID-token claims and cloud trust policies
|
|
1102
|
+
|
|
1103
|
+
A cloud provider's OIDC trust policy decides which builds may assume a role. The
|
|
1104
|
+
token below is what your policy matches on, so read this section before you
|
|
1105
|
+
write one.
|
|
1106
|
+
|
|
1107
|
+
### The claim set
|
|
1108
|
+
|
|
1109
|
+
| Claim | Value |
|
|
1110
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
1111
|
+
| `iss` | Your orchestrator's provenance issuer |
|
|
1112
|
+
| `aud` | The audience you asked for |
|
|
1113
|
+
| `sub` | The build identity — see the two shapes below |
|
|
1114
|
+
| `repository` | `owner/repo` the run acted on |
|
|
1115
|
+
| `ref` | The branch or tag the run PRESENTS. For a pull request this is the **base** branch, not the contributor's branch |
|
|
1116
|
+
| `base_ref` | The same value as `ref`, named the way GitHub Actions names it |
|
|
1117
|
+
| `head_ref` | The pull request's HEAD branch; `''` for a non-PR run |
|
|
1118
|
+
| `head_repository` | `owner/repo` of the pull-request HEAD — the contributor's fork for a fork PR; `''` for a non-PR run |
|
|
1119
|
+
| `is_fork` | `'true'`, `'false'`, or `'unresolved'` |
|
|
1120
|
+
| `event_name` | The event that started the run (`push`, `pull_request:opened`, `schedule`, …) |
|
|
1121
|
+
| `trust_tier` | The resolved trust tier of the triggering actor, or `'unresolved'` |
|
|
1122
|
+
| `actor` | Provider login of the triggering actor |
|
|
1123
|
+
| `sha` | The run's commit |
|
|
1124
|
+
| `workflow_ref` | `<workflow name>@<sha>` |
|
|
1125
|
+
| `kici_run_id` / `kici_job_id` | The run and job this token was minted for |
|
|
1126
|
+
| `org_id` | Your organization id |
|
|
1127
|
+
|
|
1128
|
+
Every claim in the table is **always present**. A value the run did not resolve
|
|
1129
|
+
is `''` or `'unresolved'`, never omitted and never guessed. That matters: an
|
|
1130
|
+
absent claim makes a `StringEquals` condition pass, which would silently remove
|
|
1131
|
+
a constraint you wrote expecting it to be enforced.
|
|
1132
|
+
|
|
1133
|
+
### The two `sub` shapes
|
|
1134
|
+
|
|
1135
|
+
```
|
|
1136
|
+
push, tag, schedule, … repo:<owner/repo>:ref:<ref>:workflow:<workflow name>
|
|
1137
|
+
pull request, review repo:<owner/repo>:pull_request
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
The pull-request shape carries **no ref segment**, mirroring GitHub Actions. A
|
|
1141
|
+
pull request's `ref` is its base branch. So a ref-bearing subject would be
|
|
1142
|
+
identical for a fork pull request targeting `main` and a trusted push to `main`.
|
|
1143
|
+
A policy pinning that subject would hand your cloud role to any contributor who
|
|
1144
|
+
opened a pull request running the same workflow.
|
|
1145
|
+
|
|
1146
|
+
**A re-run keeps the shape of the run it repeats.** Re-running a pull-request
|
|
1147
|
+
run presents `repo:<owner/repo>:pull_request`, because it rebuilds the same
|
|
1148
|
+
commit from the same source. Its `event_name` claim still reads `rerun` — that
|
|
1149
|
+
claim says what started the run, while `sub` says which identity the run
|
|
1150
|
+
presents. A policy that pins the branch-shaped subject therefore does not match
|
|
1151
|
+
a re-run of a pull request, which is the same protection the first run gets.
|
|
1152
|
+
|
|
1153
|
+
### A worked AWS trust policy
|
|
1154
|
+
|
|
1155
|
+
Pin `sub`, and pin the fork context too. `sub` alone tells you a pull request
|
|
1156
|
+
ran; it does not tell you whose code ran.
|
|
1157
|
+
|
|
1158
|
+
```json
|
|
1159
|
+
{
|
|
1160
|
+
"Version": "2012-10-17",
|
|
1161
|
+
"Statement": [
|
|
1162
|
+
{
|
|
1163
|
+
"Effect": "Allow",
|
|
1164
|
+
"Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/orch.example.com" },
|
|
1165
|
+
"Action": "sts:AssumeRoleWithWebIdentity",
|
|
1166
|
+
"Condition": {
|
|
1167
|
+
"StringEquals": {
|
|
1168
|
+
"orch.example.com:aud": "sts.amazonaws.com",
|
|
1169
|
+
"orch.example.com:sub": "repo:acme/app:ref:main:workflow:deploy",
|
|
1170
|
+
"orch.example.com:is_fork": "false",
|
|
1171
|
+
"orch.example.com:head_repository": "acme/app",
|
|
1172
|
+
"orch.example.com:trust_tier": "trusted"
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
]
|
|
1177
|
+
}
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
This grants the role only to a run on `main` in `acme/app`, from code in that
|
|
1181
|
+
same repository, triggered by an actor your orchestrator resolved as trusted.
|
|
1182
|
+
A fork pull request fails on all three of the extra conditions, and a run whose
|
|
1183
|
+
context did not resolve fails too — `'unresolved'` matches none of them, so the
|
|
1184
|
+
policy fails closed.
|
|
1185
|
+
|
|
1186
|
+
To let a same-repo pull request assume the role, add a second statement pinning
|
|
1187
|
+
`"sub": "repo:acme/app:pull_request"` alongside `"is_fork": "false"` and
|
|
1188
|
+
`"head_repository": "acme/app"`.
|
|
1189
|
+
|
|
1190
|
+
### Migrating an existing policy
|
|
1191
|
+
|
|
1192
|
+
If you already pin a ref-bearing `sub` for pull-request runs, that policy stops
|
|
1193
|
+
matching once you upgrade — which is the fix, because it was matching runs it
|
|
1194
|
+
should not have. Move it to `repo:<owner/repo>:pull_request` plus the fork
|
|
1195
|
+
conditions above. The same move covers a re-run of a pull request, which
|
|
1196
|
+
presents the pull-request subject too.
|
|
1197
|
+
|
|
1198
|
+
While you migrate, `KICI_OIDC_LEGACY_PR_SUB=1` on the orchestrator restores the
|
|
1199
|
+
old subject. It restores the collision with it, so treat it as a short bridge,
|
|
1200
|
+
not a setting. See [deprecations](https://docs.kici.dev/user/deprecations/).
|
|
1201
|
+
|
|
1071
1202
|
## Verifying an attestation
|
|
1072
1203
|
|
|
1073
1204
|
Verify a bundle with the `kici verify-attestation` command. It establishes the
|