@kici-dev/compiler 0.6.1 → 0.8.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 +2 -2
- package/dist/commands/compile.js +5 -1
- package/dist/commands/doctor.js +8 -2
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +77 -12
- package/dist/commands/local.d.ts +9 -2
- package/dist/commands/local.js +14 -4
- 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 +4 -0
- package/dist/commands/run.js +5 -2
- package/dist/commands/runs/logs.js +3 -2
- 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 +73 -87
- package/dist/llm-context/llms-cli-remote.txt +53 -8
- package/dist/llm-context/llms-cli.txt +71 -36
- package/dist/llm-context/llms-features-execution.txt +52 -6
- package/dist/llm-context/llms-features.txt +137 -6
- package/dist/llm-context/llms-full.txt +558 -180
- package/dist/llm-context/llms-getting-started.txt +5 -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 +33 -18
- package/dist/llm-context/llms-sdk.txt +47 -7
- package/dist/llm-context/llms.txt +8 -8
- package/dist/local-plane/orchestrator-process.d.ts +0 -8
- package/dist/local-plane/orchestrator-process.js +6 -14
- package/dist/local-plane/paths.d.ts +1 -0
- package/dist/local-plane/paths.js +1 -0
- package/dist/local-plane/plane-log.d.ts +27 -0
- package/dist/local-plane/plane-log.js +39 -0
- package/dist/local-plane/plane-manager.js +2 -2
- package/dist/local-plane/plane-trigger.d.ts +28 -0
- package/dist/local-plane/plane-trigger.js +57 -2
- package/dist/local-plane/postgres.js +9 -6
- package/dist/local-plane/run-follow.js +2 -1
- 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/remote/output/streaming.d.ts +12 -0
- package/dist/remote/output/streaming.js +20 -1
- package/dist/remote/platform-client.d.ts +2 -0
- package/dist/templates/package-json.d.ts +9 -7
- package/dist/templates/package-json.js +11 -9
- 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
|
@@ -29,7 +29,7 @@ interface LocalConfig {
|
|
|
29
29
|
|
|
30
30
|
### SharedConfig
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Settings stored in the PostgreSQL `config_versions` table, written and read by the `/admin/config` routes and their `kici-admin config` commands:
|
|
33
33
|
|
|
34
34
|
```typescript
|
|
35
35
|
interface SharedConfig {
|
|
@@ -73,11 +73,13 @@ interface AppConfig {
|
|
|
73
73
|
|
|
74
74
|
### How they merge
|
|
75
75
|
|
|
76
|
+
`resolveFullConfig()` takes a `LocalConfig` and an optional `SharedConfig` and merges them:
|
|
77
|
+
|
|
76
78
|
```
|
|
77
79
|
defaults (getDefaults())
|
|
78
80
|
|
|
|
79
81
|
v
|
|
80
|
-
SharedConfig (
|
|
82
|
+
SharedConfig (argument) ──deepMerge──> merged layer 1+2
|
|
81
83
|
|
|
|
82
84
|
v
|
|
83
85
|
LocalConfig (from YAML) ──deepMerge──> merged layer 1+2+3
|
|
@@ -94,29 +96,48 @@ appConfigSchema.safeParse() ──validate──> typed AppConfig
|
|
|
94
96
|
|
|
95
97
|
The `deepMerge` function merges objects recursively, replaces arrays (does not merge item-by-item), and skips `undefined`/`null` source values (they do not override existing values).
|
|
96
98
|
|
|
99
|
+
**The `SharedConfig` argument is `null` in the shipped wiring.** `ConfigReloader` is the only non-test caller of `resolveFullConfig()`, and it is constructed with `sharedStore: null`. So the DB layer is skipped and the effective chain is defaults → YAML → env. The `config_versions` table is read by the `/admin/config` write and inspection routes, by `kici-admin rotate-key`, and by the cluster join flow — never by a running orchestrator's own config.
|
|
100
|
+
|
|
97
101
|
## Resolution chain
|
|
98
102
|
|
|
99
|
-
###
|
|
103
|
+
### Startup
|
|
104
|
+
|
|
105
|
+
`server.ts` and `standalone.ts` both call `loadConfig()`, which parses `KICI_*` environment variables against the flat schema in `config.ts`. No YAML file and no database row participates:
|
|
100
106
|
|
|
101
107
|
```
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
Phase 2 (full merge):
|
|
113
|
-
defaults -> DB -> YAML -> env
|
|
114
|
-
|
|
|
115
|
-
v
|
|
116
|
-
resolveFullConfig() -> AppConfig
|
|
108
|
+
Process start
|
|
109
|
+
|
|
|
110
|
+
v
|
|
111
|
+
loadConfig() -> envDef.parse(process.env) -> AppConfig
|
|
112
|
+
|
|
|
113
|
+
v
|
|
114
|
+
Connect to PostgreSQL, run migrations
|
|
115
|
+
|
|
|
116
|
+
v
|
|
117
|
+
Start server (HTTP, WS, scaler, cluster)
|
|
117
118
|
```
|
|
118
119
|
|
|
119
|
-
|
|
120
|
+
The database URL therefore has to be an environment variable: the orchestrator needs it to reach PostgreSQL, and the shared config lives in PostgreSQL.
|
|
121
|
+
|
|
122
|
+
### Reload
|
|
123
|
+
|
|
124
|
+
`resolveLocalConfig()` and `resolveFullConfig()` run on the reload path, not at startup:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
SIGHUP / POST /admin/config/reload / kici-admin config reload
|
|
128
|
+
|
|
|
129
|
+
v
|
|
130
|
+
resolveLocalConfig() -> YAML file + KICI_ env overlay
|
|
131
|
+
|
|
|
132
|
+
v
|
|
133
|
+
resolveFullConfig(local, null) -> defaults -> YAML -> env -> AppConfig
|
|
134
|
+
|
|
|
135
|
+
v
|
|
136
|
+
Hold databaseUrl, port, instanceId and storage at their startup values
|
|
137
|
+
|
|
|
138
|
+
v
|
|
139
|
+
Atomic swap into ConfigReloader.currentConfig
|
|
140
|
+
```
|
|
120
141
|
|
|
121
142
|
### Env var processing
|
|
122
143
|
|
|
@@ -128,43 +149,6 @@ Environment variables are processed in two stages:
|
|
|
128
149
|
|
|
129
150
|
Type coercion is applied based on known field types: numeric fields are parsed as numbers, boolean fields are compared against `"true"`, all others remain strings.
|
|
130
151
|
|
|
131
|
-
## Two-phase bootstrap
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
┌─────────────────┐
|
|
135
|
-
│ Process Start │
|
|
136
|
-
└────────┬────────┘
|
|
137
|
-
│
|
|
138
|
-
▼
|
|
139
|
-
┌─────────────────┐
|
|
140
|
-
│ Load YAML + │ resolveLocalConfig()
|
|
141
|
-
│ Env Overrides │ -> databaseUrl, instanceId, port, mode
|
|
142
|
-
└────────┬────────┘
|
|
143
|
-
│
|
|
144
|
-
▼
|
|
145
|
-
┌─────────────────┐
|
|
146
|
-
│ Connect to │ PostgreSQL
|
|
147
|
-
│ Database │ Run migrations
|
|
148
|
-
└────────┬────────┘
|
|
149
|
-
│
|
|
150
|
-
▼
|
|
151
|
-
┌─────────────────┐
|
|
152
|
-
│ Load Shared │ SharedConfigStore.getLatest()
|
|
153
|
-
│ Config from DB │ -> decrypt -> SharedConfig
|
|
154
|
-
└────────┬────────┘
|
|
155
|
-
│
|
|
156
|
-
▼
|
|
157
|
-
┌─────────────────┐
|
|
158
|
-
│ Full Merge │ resolveFullConfig(local, db, env)
|
|
159
|
-
│ + Validate │ -> AppConfig
|
|
160
|
-
└────────┬────────┘
|
|
161
|
-
│
|
|
162
|
-
▼
|
|
163
|
-
┌─────────────────┐
|
|
164
|
-
│ Start Server │ HTTP, WS, scaler, cluster
|
|
165
|
-
└─────────────────┘
|
|
166
|
-
```
|
|
167
|
-
|
|
168
152
|
## DB schema
|
|
169
153
|
|
|
170
154
|
### config_versions table
|
|
@@ -264,7 +248,7 @@ flowchart TD
|
|
|
264
248
|
trigger --> execute["executeReload()<br/>boolean mutex"]
|
|
265
249
|
|
|
266
250
|
execute --> resolveLocal["resolveLocalConfig()"]
|
|
267
|
-
execute --> getLatest["getLatest (DB)"]
|
|
251
|
+
execute --> getLatest["getLatest (DB)<br/>skipped: sharedStore is null"]
|
|
268
252
|
execute --> resolveFull["resolveFullConfig()<br/>(merge + validate)"]
|
|
269
253
|
|
|
270
254
|
resolveLocal --> check["Check restart-required fields"]
|
|
@@ -283,26 +267,26 @@ flowchart TD
|
|
|
283
267
|
- **Mutex:** Boolean flag prevents concurrent reloads. Second reload returns `{ success: false, errors: ["Reload already in progress"] }`.
|
|
284
268
|
- **Debounce:** Rapid triggers (e.g., multiple SIGHUP signals) are collapsed into a single reload with a 500ms window.
|
|
285
269
|
- **Validation before swap:** The new config must pass full schema validation. On failure, the old config is preserved and an error is logged.
|
|
286
|
-
- **Restart-required detection:**
|
|
270
|
+
- **Restart-required detection:** `databaseUrl`, `port`, `instanceId` and `storage` are compared. If changed, the old values are preserved in the applied config and a warning is logged.
|
|
287
271
|
- **No crash on failure:** The orchestrator always keeps running with the old config if anything goes wrong during reload.
|
|
288
272
|
|
|
289
273
|
### Subsystem callbacks
|
|
290
274
|
|
|
291
275
|
The `ConfigReloader` uses a dependency injection pattern with callbacks for subsystem re-initialization:
|
|
292
276
|
|
|
293
|
-
| Callback | When Called | Purpose
|
|
294
|
-
| --------------------- | ----------------------------- |
|
|
295
|
-
| `onProviderChange` | Provider config changed | Reserved callback
|
|
296
|
-
| `onScalerReload` | Always on successful reload | Reload scaler YAML config
|
|
297
|
-
| `onPlatformReconnect` | Platform URL or token changed |
|
|
298
|
-
| `onConfigApplied` | Always on successful reload | Atomic config reference swap, increment local config version
|
|
277
|
+
| Callback | When Called | Purpose |
|
|
278
|
+
| --------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
279
|
+
| `onProviderChange` | Provider config changed | Reserved callback. Providers are DB-managed via the sources table, so the change detector always reports no change |
|
|
280
|
+
| `onScalerReload` | Always on successful reload | Reload scaler YAML config, from the path the process started with |
|
|
281
|
+
| `onPlatformReconnect` | Platform URL or token changed | Logs that the Platform connection settings changed. The connection is not re-established; `standalone.ts` registers no handler |
|
|
282
|
+
| `onConfigApplied` | Always on successful reload | Atomic config reference swap, increment local config version |
|
|
299
283
|
|
|
300
284
|
### Prometheus metrics
|
|
301
285
|
|
|
302
|
-
| Metric | Type | Labels | Description
|
|
303
|
-
| ------------------------------- | ------- | ----------------------------------------------------------------------- |
|
|
304
|
-
| `kici_orch_config_reload_total` | Counter | `result` (attempted/success/failed), `source` (sighup/http/cluster/cli) | Config reload attempts and outcomes
|
|
305
|
-
| `kici_orch_config_version` | Gauge | -- |
|
|
286
|
+
| Metric | Type | Labels | Description |
|
|
287
|
+
| ------------------------------- | ------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
288
|
+
| `kici_orch_config_reload_total` | Counter | `result` (attempted/success/failed), `source` (sighup/http/cluster/cli) | Config reload attempts and outcomes |
|
|
289
|
+
| `kici_orch_config_version` | Gauge | -- | Shared config version from the DB. Set only when the reload path reads a version, so it carries no value today |
|
|
306
290
|
|
|
307
291
|
## Multi-Provider
|
|
308
292
|
|
|
@@ -334,40 +318,42 @@ Each source record contains its own `appId` and `privateKey` (stored as scoped s
|
|
|
334
318
|
|
|
335
319
|
### Heartbeat config version
|
|
336
320
|
|
|
337
|
-
In clustered deployments, each orchestrator includes its
|
|
321
|
+
In clustered deployments, each orchestrator includes its config version in Raft heartbeat metadata via the `configVersion` optional field on the `peerHeartbeatSchema`.
|
|
322
|
+
|
|
323
|
+
**That number is a local reload counter, not a shared config version.** `onConfigApplied` increments it on every successful reload and publishes the new value to the peer registry. It counts how many times this instance has reloaded.
|
|
338
324
|
|
|
339
325
|
When the `PeerRegistry` processes a heartbeat:
|
|
340
326
|
|
|
341
327
|
1. Compare `localConfigVersion` with `peer.configVersion`
|
|
342
328
|
2. If `peer.configVersion > localConfigVersion` AND both are > 0:
|
|
343
329
|
- Invoke the `onConfigVersionBehind` callback
|
|
344
|
-
- This triggers a config reload
|
|
330
|
+
- This triggers a config reload, which re-reads the environment and the local YAML file
|
|
345
331
|
|
|
346
332
|
### Auto-remediation flow
|
|
347
333
|
|
|
348
334
|
```
|
|
349
|
-
Orchestrator A (
|
|
335
|
+
Orchestrator A (reloaded 5x) Orchestrator B (reloaded 3x)
|
|
350
336
|
│ │
|
|
351
337
|
│──── heartbeat(configVersion=5) ────>│
|
|
352
338
|
│ │
|
|
353
339
|
│ compare: 5 > 3
|
|
354
|
-
│ trigger reload
|
|
340
|
+
│ trigger reload
|
|
355
341
|
│ │
|
|
356
|
-
│ resolveFullConfig()
|
|
357
|
-
│ ->
|
|
342
|
+
│ resolveFullConfig(local, null)
|
|
343
|
+
│ -> counter becomes 4
|
|
358
344
|
│ │
|
|
359
|
-
│<── heartbeat(configVersion=
|
|
345
|
+
│<── heartbeat(configVersion=4) ──────│
|
|
360
346
|
│ │
|
|
361
|
-
│ both at version 5 ✓ │
|
|
362
347
|
```
|
|
363
348
|
|
|
349
|
+
B converges on A's count only after it has reloaded as many times as A has. Each instance reads its own environment and its own YAML file, so the two agree on content only when those inputs agree.
|
|
350
|
+
|
|
364
351
|
### Guard conditions
|
|
365
352
|
|
|
366
353
|
- Version comparison only triggers when **both** local and peer versions are > 0
|
|
367
354
|
- This prevents false triggers from:
|
|
368
|
-
-
|
|
369
|
-
- Newly started orchestrators before their first
|
|
370
|
-
- The `localConfigVersion` is a monotonically incrementing local counter (incremented on each successful reload)
|
|
355
|
+
- Orchestrators that do not report `configVersion` (field is optional, defaults to 0)
|
|
356
|
+
- Newly started orchestrators before their first reload
|
|
371
357
|
|
|
372
358
|
## See also
|
|
373
359
|
|
|
@@ -398,7 +384,7 @@ GitHub --> Platform Relay --> Orchestrator --> Agent
|
|
|
398
384
|
|
|
399
385
|
1. **Provider sends webhook** to the Platform relay endpoint.
|
|
400
386
|
2. **Platform routes the webhook** to the right orchestrator over WebSocket and forwards the body bytes verbatim. Platform never sees customer HMAC secrets — signature verification happens entirely on the orchestrator after reassembly.
|
|
401
|
-
3. **Orchestrator verifies signature** (HMAC-SHA256 against per-source webhook secret, with dual-secret rotation support).
|
|
387
|
+
3. **Orchestrator admits the delivery**, then **verifies the signature** (HMAC-SHA256 against per-source webhook secret, with dual-secret rotation support). Admission runs first, on the routing key alone: when the ingest admission controller sheds, the orchestrator records an `event_log` breadcrumb with status `shed` and ACKs `shed_retry_later`, which the Platform answers as **429** with `Retry-After`. See [ingest admission shed](https://docs.kici.dev/architecture/webhooks/webhook-delivery/#ingest-admission-shed-step-3).
|
|
402
388
|
4. **Orchestrator dedup check** against dual-layer `DedupCache` (in-memory set + `dedup_cache` DB table).
|
|
403
389
|
5. **Orchestrator resolves provider** by looking up the provider bundle from the `ProviderRegistry` using `getByRoutingKey()` (exact match first, falls back to provider type prefix for backward compatibility). Skips processing if the provider is unknown.
|
|
404
390
|
6. **Orchestrator normalizes** the webhook via the provider's `WebhookNormalizer` (extracts branch, event type, action, sender).
|
|
@@ -525,7 +511,7 @@ Build Job Dispatch --> Build Agent (kici:role:builder + matching kici:os:/kici:a
|
|
|
525
511
|
| |-- npm ci in .kici/
|
|
526
512
|
| |-- Pack .kici/ source (portable tar.gz, excludes node_modules)
|
|
527
513
|
| |-- Pack .kici/node_modules (portable tar.gz)
|
|
528
|
-
| |-- Upload source tarball to cache (source/{
|
|
514
|
+
| |-- Upload source tarball to cache (source/v2/{orgId}/{sourceTarDigest}.tar.gz)
|
|
529
515
|
| |-- Upload deps tarball to cache (deps/{plat}-{arch}/{depsHash}.tar.gz)
|
|
530
516
|
| |-- Upload deps companion .hash file
|
|
531
517
|
| |-- Report success (cache.upload.complete × 2)
|
|
@@ -600,7 +586,7 @@ Dep cache misses alone do **not** trigger a build job. Deps are platform-specifi
|
|
|
600
586
|
|
|
601
587
|
### Cross-source / no-contentHash workflows
|
|
602
588
|
|
|
603
|
-
- **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
|
|
589
|
+
- **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 41.
|
|
604
590
|
- **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.
|
|
605
591
|
|
|
606
592
|
### Build deduplication
|
|
@@ -639,7 +625,7 @@ Both source and dep caches use `S3CacheStorage` as the sole backend. The `CacheS
|
|
|
639
625
|
|
|
640
626
|
Cache keys reflect that source tarballs and deps have different platform characteristics:
|
|
641
627
|
|
|
642
|
-
- **Source:** `source/{
|
|
628
|
+
- **Source:** `source/v2/{orgId}/{sourceTarDigest}.tar.gz`, with a `source/v2/{orgId}/{contentHash}.hash` pointer — platform-agnostic, and scoped to the owning organization so two repositories with matching `.kici/` trees never share one object. Raw TypeScript source is identical regardless of CPU architecture, so one entry is shared across all platforms. `contentHash` is the per-workflow hash from the lock file (`SHA-256(COMPILE_SCHEMA_VERSION + ":" + rawSource [+ "\0" + assetDigest])`, where `COMPILE_SCHEMA_VERSION = 7` and line endings are normalized to LF so the hash agrees across platforms).
|
|
643
629
|
- **Deps:** `deps/{platform}-{arch}/{depsHash}.tar.gz`, with a
|
|
644
630
|
`deps/{platform}-{arch}/{lockfileHash}.hash` pointer holding that hash — the
|
|
645
631
|
tarball is addressed by its own content, so two builds sharing a lock file
|
|
@@ -1370,14 +1356,14 @@ Shared business logic used by all three tiers. Single source of truth for cross-
|
|
|
1370
1356
|
- Label utilities (platform label derivation, runsOn normalization, `kici:*` set-only reserved namespace, role labels)
|
|
1371
1357
|
- Host inventory (the canonical queryable host-roster schema shared by the orchestrator's roster store, the agent-facing inventory API, and the SDK's `ctx.kici.inventory`)
|
|
1372
1358
|
- Audit policy and retention (per-action access-log sampling, warm-retention windows for cold-store eligibility, federated activity row schema)
|
|
1373
|
-
- Scaler backend type enum (`container`, `bare-metal`, `firecracker`, `kubernetes`, `event`) and the reserved `kici.` event-name prefix that keeps a user step from forging a system event
|
|
1359
|
+
- Scaler backend type enum (`container`, `bare-metal`, `firecracker`, `kubernetes`, `event`; the orchestrator config rejects `kubernetes`) and the reserved `kici.` event-name prefix that keeps a user step from forging a system event
|
|
1374
1360
|
- Job resource vocabulary (the requests/limits shape the SDK accepts, the compiler validates and emits, the orchestrator uses for capacity math and kernel-side enforcement, and the dashboard displays)
|
|
1375
1361
|
- Registration trigger type enum (registerable trigger discriminator)
|
|
1376
1362
|
- Sandbox capability set (the Linux capability names a container sandbox may add or drop, shared by the SDK validator, the compiler, and the dispatch resolver)
|
|
1377
1363
|
- Plan tier vocabulary (the hosted plan tiers and the purchasable subset, shared by the Platform and the browser dashboard)
|
|
1378
1364
|
- Infrastructure alert vocabulary (the diagnostics alert types and severities the Platform mints and the dashboard and `kici` CLI render)
|
|
1379
1365
|
- Metric catalog (the generated Prometheus metric inventory, its naming policy, and metric-kind compatibility checks)
|
|
1380
|
-
- Bundler config (shared
|
|
1366
|
+
- Bundler config (the shared workflow-bundle configuration factory on the barrel; the agent runtime uses the `@kici-dev/core/ts-loader-hook` to transform TypeScript on import, so no runtime path bundles a workflow)
|
|
1381
1367
|
|
|
1382
1368
|
> Source: `packages/engine/src/`
|
|
1383
1369
|
|
|
@@ -1397,7 +1383,7 @@ It also runs the **local dev plane** -- an on-demand, fully local execution stac
|
|
|
1397
1383
|
|
|
1398
1384
|
### `@kici-dev/core`
|
|
1399
1385
|
|
|
1400
|
-
Light shared utilities with no server-side dependencies. It provides JSON-structured logging, error helpers, async-local-storage request context, and human-readable formatting (`formatBytes`/`formatDuration`/`formatUptime`). It also provides cryptographic helpers (`sha256`/`sha256File`/`deriveSharedSecret` plus symmetric encrypt/decrypt), retry-backoff computation, and the shared diagnostics-result contract. The rest of its surface ships as subpath entry points: the temp-directory allocator and its garbage collector, package-manager detection, CI-environment detection, and the idempotent-step runner (the check / confirm / apply primitive behind idempotent steps). Finally it supplies 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.
|
|
1386
|
+
Light shared utilities with no server-side dependencies. It provides JSON-structured logging, error helpers, async-local-storage request context, and human-readable formatting (`formatBytes`/`formatDuration`/`formatUptime`). It also provides cryptographic helpers (`sha256`/`sha256File`/`deriveSharedSecret` plus symmetric encrypt/decrypt), retry-backoff computation, and the shared diagnostics-result contract. The rest of its surface ships as subpath entry points: the temp-directory allocator and its garbage collector, package-manager detection, CI-environment detection, and the idempotent-step runner (the check / confirm / apply primitive behind idempotent steps). One further subpath holds the `.kici/` source digest: the single content-hash definition the compiler writes into the lock file and the agent recomputes as its drift gate. Finally it supplies 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.
|
|
1401
1387
|
|
|
1402
1388
|
> Source: `packages/core/src/`
|
|
1403
1389
|
|
|
@@ -1479,7 +1465,7 @@ KiCI uses three WebSocket layers for real-time communication.
|
|
|
1479
1465
|
|
|
1480
1466
|
### Platform ↔ Orchestrator
|
|
1481
1467
|
|
|
1482
|
-
The orchestrator connects outbound to the Platform WebSocket endpoint. After authentication (API key validated via SHA-256 hash lookup), the connection is used for webhook relay, execution telemetry (events, status, logs), source registration, and peer discovery.
|
|
1468
|
+
The orchestrator connects outbound to the Platform WebSocket endpoint. After authentication (API key validated via SHA-256 hash lookup), the connection is used for webhook relay, execution telemetry (events, status, logs), source registration, and peer discovery. Peer discovery is matchmaking only: the Platform pushes a `peer.update` membership list to every orchestrator sharing a routing key, and the orchestrators then connect to each other directly. Inter-orchestrator traffic such as `job.reroute` never transits the Platform.
|
|
1483
1469
|
|
|
1484
1470
|
### Orchestrator ↔ Orchestrator (P2P)
|
|
1485
1471
|
|
|
@@ -126,6 +126,12 @@ If you belong to a single organization, the org is resolved automatically. If
|
|
|
126
126
|
you belong to several, pass an `orgId` argument to any tool (use `list_orgs` to
|
|
127
127
|
find it).
|
|
128
128
|
|
|
129
|
+
Only an **active** membership counts: an organization you have been suspended
|
|
130
|
+
in, one that has been disabled, and one that has been deleted are all skipped.
|
|
131
|
+
So a single active membership alongside a disabled one still resolves
|
|
132
|
+
automatically, and a tool call against an organization you are suspended in is
|
|
133
|
+
refused with the same message the dashboard gives.
|
|
134
|
+
|
|
129
135
|
### Limits and pagination
|
|
130
136
|
|
|
131
137
|
The MCP server applies a few bounds so an agent loop can't overwhelm the shared
|
|
@@ -215,7 +221,7 @@ you hold, so an agent cannot escalate beyond its creator.
|
|
|
215
221
|
|
|
216
222
|
**Repository scope comes along too.** If your role is restricted to a set of
|
|
217
223
|
repositories, an agent token you mint is restricted to the same set. Runs
|
|
218
|
-
outside it are
|
|
224
|
+
outside it are not there: they are filtered out of `list_runs`,
|
|
219
225
|
`cancel_runs_by_branch` skips them, and naming one directly answers "not found"
|
|
220
226
|
— the same answer a run id that does not exist gets, so an agent cannot use the
|
|
221
227
|
tools to discover which repositories it is missing. Your organization's audit
|
|
@@ -458,14 +464,18 @@ Tokens authenticate; RBAC authorizes. Every org-scoped route runs `orgContextMid
|
|
|
458
464
|
|
|
459
465
|
### Configurable surfaces
|
|
460
466
|
|
|
461
|
-
The dashboard is a browser SPA on top of the same `/api/v1/*` surface, so
|
|
467
|
+
The dashboard is a browser SPA on top of the same `/api/v1/*` surface, so nearly everything you can configure in the dashboard you can configure over HTTP. The exception is a short list of routes that only a **browser session** may call, marked † below. The mounted route groups include:
|
|
462
468
|
|
|
463
|
-
- **Auth & identity:** `/cli/exchange-token
|
|
464
|
-
- **Org & membership:** `/orgs
|
|
469
|
+
- **Auth & identity:** `/cli/exchange-token`†, `/pats`, `/user`, `/identity-links`, `/identity-links/:id` (DELETE)†, `/github-oauth`, `/auth/github/link`†, `/invites`, `/invites/pending`, `/invites/:inviteId/{accept,decline}`†
|
|
470
|
+
- **Org & membership:** `/orgs` (POST)†, `/orgs/:customerId`, `/orgs/:customerId/{members,roles,api-keys,orchestrator-keys,service-accounts,billing,trust-policies}`
|
|
465
471
|
- **Workflows & runs:** `/orgs/:customerId/{runs,registrations,workflows,held-runs,contexts,secrets,global-workflows}`
|
|
466
472
|
- **Webhooks & event log:** `/orgs/:customerId/{sources,webhook-endpoints,event-log}`
|
|
467
473
|
- **Diagnostics & activity:** `/orgs/:customerId/{diagnostics,activity,access-log}`
|
|
468
474
|
|
|
475
|
+
**† Browser session only.** These routes answer **403** with `This endpoint requires an interactive login` to a `kici_pat_`, `kici_sk_` or `kici_sa_` token. They are the routes that create an organization, join or decline one, unlink a provider identity, start a GitHub account link, and exchange your session for a personal access token. Each mints a credential wider than the credential presenting it, or changes which organizations and provider identities your account reaches. A `kici_sk_` or `kici_sa_` token is bound to one organization and one permission set, so letting it take those actions would hand it access it was never granted. Do them in the dashboard, or with `kici login`, which runs the browser flow for you.
|
|
476
|
+
|
|
477
|
+
`POST /pats` is the one route in between: it accepts a browser session **or** an existing `kici_pat_` (so `kici pat create` keeps working), and refuses `kici_sk_` and `kici_sa_`. A token minted from another token can never be wider than that token. The new token is capped by your own permissions **and** by the scopes of the token you called with, whether or not you pass `permissions` explicitly. Mint from an unscoped session when you need a broader token.
|
|
478
|
+
|
|
469
479
|
The full route tree is the source of truth — every method, request schema, and response schema is enumerated server-side. There is currently no auto-generated OpenAPI spec; the typed `DashboardApiType` export is the canonical contract for TypeScript clients.
|
|
470
480
|
|
|
471
481
|
### Calling the API
|
|
@@ -484,11 +494,16 @@ curl -sS \
|
|
|
484
494
|
|
|
485
495
|
**Browser console (after dashboard login):**
|
|
486
496
|
|
|
497
|
+
Mint a personal access token and pass it explicitly. Do not script against the
|
|
498
|
+
dashboard's own session token. That token is short-lived and tied to your
|
|
499
|
+
identity-provider session, so anything built on it stops working at the next
|
|
500
|
+
renewal or sign-out.
|
|
501
|
+
|
|
487
502
|
```js
|
|
488
|
-
|
|
489
|
-
const
|
|
503
|
+
// kici pat create --name console --expires-in-days 1 → prints the token
|
|
504
|
+
const token = 'kici_pat_...';
|
|
490
505
|
const res = await fetch('/<deployment-slug>/api/v1/orgs/<your-org-id>/runs?limit=5', {
|
|
491
|
-
headers: { Authorization: `Bearer ${
|
|
506
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
492
507
|
});
|
|
493
508
|
console.log(await res.json());
|
|
494
509
|
```
|
|
@@ -513,6 +528,12 @@ The CLI stores authentication data in `~/.kici/config` with `0600` permissions (
|
|
|
513
528
|
- Routing key for webhook source identification
|
|
514
529
|
- API key, when you logged in with `--token`
|
|
515
530
|
|
|
531
|
+
The web dashboard holds none of these. It keeps only the tokens of your current
|
|
532
|
+
sign-in, in browser storage for the tab's origin. It does not request offline
|
|
533
|
+
access, so those tokens die with your identity-provider session instead of
|
|
534
|
+
staying valid for weeks. Its origin also serves a Content-Security-Policy that
|
|
535
|
+
restricts which scripts run and which hosts the page may contact.
|
|
536
|
+
|
|
516
537
|
## Troubleshooting
|
|
517
538
|
|
|
518
539
|
### Browser doesn't open
|
|
@@ -717,10 +738,14 @@ kici init --private-registry https://npm.pkg.github.com/ \
|
|
|
717
738
|
types/ # Directory for generated type declarations (kici types)
|
|
718
739
|
package.json # Dependencies (@kici-dev/sdk)
|
|
719
740
|
tsconfig.json # TypeScript configuration (includes types/**/*.d.ts)
|
|
741
|
+
.gitignore # Keeps the generated types/ declarations untracked
|
|
742
|
+
.kiciignore # Paths the workflow content hash does not cover
|
|
720
743
|
AGENTS.md # LLM authoring context (skip with --no-agents-md)
|
|
721
744
|
.kiciignore # Default exclusion patterns for test uploads
|
|
722
745
|
```
|
|
723
746
|
|
|
747
|
+
The two `.kiciignore` files are unrelated. The one inside `.kici/` declares which paths the per-workflow content hash skips — see [Lock file and workflow drift](https://docs.kici.dev/user/lock-file-and-drift/#files-the-content-hash-skips-kicikiciignore). The one at the repo root selects which working-tree files a remote run uploads. Neither is overwritten when it already exists.
|
|
748
|
+
|
|
724
749
|
`AGENTS.md` is written by default (the interactive prompt defaults to yes, and CI / non-interactive runs write it). An existing `.kici/AGENTS.md` is never overwritten, so hand edits survive a re-run.
|
|
725
750
|
|
|
726
751
|
In interactive mode (TTY), `kici init` prompts you to:
|
|
@@ -736,7 +761,7 @@ In interactive mode (TTY), `kici init` prompts you to:
|
|
|
736
761
|
|
|
737
762
|
**Standalone vs workspace integration:** by default `kici init` scaffolds a self-contained `.kici/` with its own `package.json`. When run inside a pnpm, npm, or yarn workspace, it offers an **integrate** option (or pass `--workspace`): `.kici/` joins the workspace, `@kici-dev/sdk` is added to your workspace-root `package.json`, and your workflows can `import` your other workspace packages (e.g. shared build or deploy utilities). In this mode there is no `.kici/package.json` — the workspace root manages dependencies, and the root install resolves the SDK. Your workflows resolve sibling packages through the workspace-root `node_modules`: under npm and yarn every workspace member is hoisted there automatically, while pnpm links only your root's declared dependencies, so under pnpm add the package you want to import to your workspace-root `dependencies` (this is how KiCI's own repository imports its packages from workflows). Pass `--standalone` to force the self-contained layout even inside a workspace. In CI / non-interactive runs the default is standalone; use `--workspace` to opt in explicitly. `--workspace` and `--standalone` are mutually exclusive, and `--workspace` errors if no workspace is found at or above the current directory.
|
|
738
763
|
|
|
739
|
-
**Development mode:** When `KICI_DEV=true` or `package.json` has `"kici": { "development": true }`, the generated `package.json`
|
|
764
|
+
**Development mode:** When `KICI_DEV=true` or `package.json` has `"kici": { "development": true }`, the generated `package.json` pins `@kici-dev/sdk` to the `latest` dist-tag so npm resolves Verdaccio's newest prerelease build.
|
|
740
765
|
|
|
741
766
|
### kici org
|
|
742
767
|
|
|
@@ -1261,6 +1286,14 @@ to see it. The bundle holds your CLI, Node and orchestrator versions, your
|
|
|
1261
1286
|
redacted configuration, and your project's workflow and lock-file state. With
|
|
1262
1287
|
`--run` it also holds the failing run's detail and logs.
|
|
1263
1288
|
|
|
1289
|
+
For every orchestrator the probe returned the bundle also records where that
|
|
1290
|
+
orchestrator's own config files live — the paths only, never the contents. An
|
|
1291
|
+
orchestrator that is offline is still listed, with no paths: the CLI reads them
|
|
1292
|
+
from the live connection, so a disconnected one has none to report.
|
|
1293
|
+
Each path is a host path as that orchestrator sees it. A container deployment
|
|
1294
|
+
names a file on the container host, which is not a file you can open from the
|
|
1295
|
+
machine that read the bundle.
|
|
1296
|
+
|
|
1264
1297
|
```bash
|
|
1265
1298
|
kici report [options]
|
|
1266
1299
|
```
|
|
@@ -1674,6 +1707,18 @@ Secrets are always sourced from your real `.kici/` directory, not from the isola
|
|
|
1674
1707
|
|
|
1675
1708
|
Pass `--in-place` to run against the real working directory instead — useful when you explicitly want in-tree execution. `--in-place` requires no git repository; the default isolated mode does, and fails with an actionable error pointing at `--in-place` when the directory is not a git repository.
|
|
1676
1709
|
|
|
1710
|
+
**If the trigger times out:**
|
|
1711
|
+
|
|
1712
|
+
The command waits up to 60 seconds for the local dev plane to create the run. If nothing appears in that window it stops and names the cause it read back from the plane:
|
|
1713
|
+
|
|
1714
|
+
| Message | What it means |
|
|
1715
|
+
| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1716
|
+
| `plane is not leader yet (election grace period)` | The plane had not finished electing itself. A single-machine plane elects within seconds; if it does not, read the plane log (`kici local logs`). |
|
|
1717
|
+
| `no kici.lock.json at <commit> in <path>` | The plane resolved no lock file for the commit the run packed, so nothing matched. Commit `.kici/kici.lock.json`, or make sure it is present in the working tree. |
|
|
1718
|
+
| `the plane recorded delivery <id> as "<status>"` | The plane processed the trigger and wrote down a terminal status for it without creating a run. The status names the stage that stopped. |
|
|
1719
|
+
| `no run appeared for delivery <id> — see <log>` | Neither the plane's cluster state nor its delivery record explained the timeout. The named log is the next place to look. |
|
|
1720
|
+
| `no run appeared for this trigger — the plane never accepted the webhook` | Every webhook POST came back without a delivery id, so the plane never queued the trigger. Check the plane is up (`kici local status`), then read the named log. |
|
|
1721
|
+
|
|
1677
1722
|
**Examples:**
|
|
1678
1723
|
|
|
1679
1724
|
```bash
|