@kici-dev/compiler 0.7.0 → 0.9.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 +5 -4
- package/dist/commands/docs.js +2 -2
- package/dist/commands/feedback.d.ts +23 -1
- package/dist/commands/feedback.js +86 -9
- package/dist/commands/index.d.ts +2 -2
- package/dist/commands/index.js +2 -2
- package/dist/commands/local.d.ts +13 -5
- package/dist/commands/local.js +19 -8
- package/dist/commands/report/identity.js +1 -1
- package/dist/commands/run-banner.d.ts +1 -1
- package/dist/commands/run-banner.js +1 -1
- package/dist/commands/run-routed.js +3 -0
- package/dist/commands/run.js +5 -2
- package/dist/commands/runs/logs.js +3 -2
- package/dist/commands/verify-attestation.js +1 -1
- package/dist/llm-context/llms-architecture.txt +9 -7
- package/dist/llm-context/llms-cli-remote.txt +27 -12
- package/dist/llm-context/llms-cli.txt +6 -4
- package/dist/llm-context/llms-features-execution.txt +9 -9
- package/dist/llm-context/llms-features.txt +452 -133
- package/dist/llm-context/llms-full.txt +561 -197
- package/dist/llm-context/llms-getting-started.txt +34 -17
- package/dist/llm-context/llms-providers.txt +2 -2
- package/dist/llm-context/llms-sdk-runtime.txt +16 -2
- package/dist/llm-context/llms-sdk.txt +7 -12
- package/dist/llm-context/llms.txt +7 -6
- package/dist/local-plane/orchestrator-process.d.ts +4 -5
- package/dist/local-plane/orchestrator-process.js +5 -1
- 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/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/agents-md.d.ts +1 -1
- package/dist/templates/agents-md.js +9 -7
- package/dist/templates/package-json.d.ts +9 -7
- package/dist/templates/package-json.js +11 -9
- package/dist/templates/workflows/hello-world.ts +1 -1
- package/dist/templates/workflows/pr-checks.ts +2 -2
- package/dist/test-runner/job-executor.js +3 -2
- package/dist/types.d.ts +12 -35
- package/dist/types.js +2 -12
- package/dist/types.test-d.d.ts +2 -0
- package/dist/types.test-d.js +63 -0
- package/dist/workflows/hello-world.ts +1 -1
- package/dist/workflows/pr-checks.ts +2 -2
- package/package.json +15 -15
- package/sbom.spdx.json +1303 -1483
|
@@ -588,7 +588,7 @@ Point to a local SDK checkout for IDE autocompletion:
|
|
|
588
588
|
{
|
|
589
589
|
"name": "my-project-kici",
|
|
590
590
|
"devDependencies": {
|
|
591
|
-
"@kici-dev/sdk": "
|
|
591
|
+
"@kici-dev/sdk": "latest"
|
|
592
592
|
},
|
|
593
593
|
"kici": {
|
|
594
594
|
"sdkPath": "../../packages/sdk"
|
|
@@ -600,7 +600,7 @@ The `sdkPath` field tells the compiler where to resolve TypeScript path mappings
|
|
|
600
600
|
|
|
601
601
|
### KICI_DEV environment variable
|
|
602
602
|
|
|
603
|
-
Set `KICI_DEV=true` to
|
|
603
|
+
Set `KICI_DEV=true` to pin the SDK to the `latest` dist-tag in generated files, which resolves prerelease builds from a local Verdaccio registry:
|
|
604
604
|
|
|
605
605
|
```bash
|
|
606
606
|
KICI_DEV=true npx kici init
|
|
@@ -845,7 +845,7 @@ Each guide is split into two parts. **Part 1** gets you a green run against your
|
|
|
845
845
|
|
|
846
846
|
## Bring a coding agent
|
|
847
847
|
|
|
848
|
-
Workflows are TypeScript, so a coding agent can write them. KiCI ships its docs in a form an agent reads directly: point yours at [llms.txt](https://kici.dev/
|
|
848
|
+
Workflows are TypeScript, so a coding agent can write them. KiCI ships its docs in a form an agent reads directly: point yours at [llms.txt](https://kici.dev/llms.txt), or pipe a task bundle straight into its context with `kici docs llm` (`kici docs llm sdk` for the authoring API, `kici docs llm patterns` for recipes). An agent briefed that way can scaffold your first workflow, compile it, and read the failure when it breaks.
|
|
849
849
|
|
|
850
850
|
If your agent finds that these docs promise something KiCI does not do, that is worth telling us — `kici feedback` prints how to report it, and [Reporting a discrepancy](https://docs.kici.dev/user/reporting-discrepancies/) is the full guide.
|
|
851
851
|
|
|
@@ -891,8 +891,10 @@ promised. You can report that.
|
|
|
891
891
|
A human reader can use this page too. The rules below exist because the tracker
|
|
892
892
|
is public and you are filing under someone else's GitHub identity.
|
|
893
893
|
|
|
894
|
-
Run `kici feedback` to print this contract in your terminal,
|
|
895
|
-
`kici feedback --json` to read it as structured data
|
|
894
|
+
Run `kici feedback` to print this contract in your terminal,
|
|
895
|
+
`kici feedback --json` to read it as structured data, or
|
|
896
|
+
`kici feedback --draft <file.json> --open` to turn a draft into the prefilled form
|
|
897
|
+
and open it.
|
|
896
898
|
|
|
897
899
|
## The rule, in one sentence
|
|
898
900
|
|
|
@@ -967,19 +969,20 @@ approval rule.
|
|
|
967
969
|
|
|
968
970
|
## What the report must carry
|
|
969
971
|
|
|
970
|
-
|
|
972
|
+
A report missing any of these fields cannot be acted on:
|
|
971
973
|
|
|
972
974
|
| Field | What it holds |
|
|
973
975
|
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
974
976
|
| What the docs or CLI advertise | The exact claim, quoted, plus its source: a docs URL or the command whose `--help` says it. |
|
|
975
977
|
| What actually happened | The real output or behaviour, quoted, with any error text. |
|
|
976
978
|
| Minimal reproduction, including setup | Every step from an empty directory: the setup commands, a minimal synthetic workflow, and the exact command you ran. |
|
|
977
|
-
|
|
|
979
|
+
| KiCI version | Output of `kici --version`. |
|
|
980
|
+
| Environment | Node version and OS. |
|
|
978
981
|
| Why this is a discrepancy | One or two sentences ruling out the likely misreads — why the docs cannot be read to match what you observed. |
|
|
979
982
|
|
|
980
983
|
The last field is the one agents skip, and it is the one that makes a report
|
|
981
984
|
usable. "The docs say `--foo` exists and it does not" is a claim. "`--foo` is
|
|
982
|
-
documented at <url>, and `kici bar --help` on
|
|
985
|
+
documented at <url>, and `kici bar --help` on the version I ran lists no such flag" is a
|
|
983
986
|
finding someone can act on in one pass.
|
|
984
987
|
|
|
985
988
|
Include setup steps whenever your reproduction needed any. A reproduction that
|
|
@@ -988,22 +991,36 @@ starts from state a maintainer cannot recreate is not a reproduction.
|
|
|
988
991
|
## Filing it
|
|
989
992
|
|
|
990
993
|
The tracker is [kici-dev/kici-public](https://github.com/kici-dev/kici-public),
|
|
991
|
-
and the form is **Agent report
|
|
994
|
+
and the form is **Agent report** (`agent_report.yml`). It is a GitHub issue
|
|
995
|
+
form, which only the browser can submit — `gh issue create` cannot fill it.
|
|
996
|
+
So you draft, and the person files.
|
|
992
997
|
|
|
993
|
-
|
|
994
|
-
|
|
998
|
+
Write the draft as JSON, one key per field of the form (`kici feedback --json`
|
|
999
|
+
lists them under `draftFields`):
|
|
1000
|
+
|
|
1001
|
+
```json
|
|
1002
|
+
{
|
|
1003
|
+
"title": "kici compile --check writes a lock file",
|
|
1004
|
+
"advertised": "https://docs.kici.dev/user/cli-reference/ says: `kici compile --check` type-checks without writing a lock file.",
|
|
1005
|
+
"observed": ".kici/kici.lock.json was written; mtime changed.",
|
|
1006
|
+
"reproduction": "$ mkdir /tmp/repro && cd /tmp/repro\n$ kici init\n$ kici compile --check",
|
|
1007
|
+
"version": "0.8.0",
|
|
1008
|
+
"environment": "Node 24.13.0, Ubuntu 24.04",
|
|
1009
|
+
"justification": "The sentence names --check as the no-write mode; no reading of it allows a write."
|
|
1010
|
+
}
|
|
995
1011
|
```
|
|
996
1012
|
|
|
997
|
-
|
|
998
|
-
the whole body, not a summary of it. File only after they say yes:
|
|
1013
|
+
Then build the prefilled form:
|
|
999
1014
|
|
|
1000
1015
|
```bash
|
|
1001
|
-
|
|
1016
|
+
kici feedback --draft draft.json --open
|
|
1002
1017
|
```
|
|
1003
1018
|
|
|
1004
|
-
The
|
|
1005
|
-
|
|
1006
|
-
|
|
1019
|
+
The command prints the form URL with every field filled in and, with
|
|
1020
|
+
`--open`, opens it. It reaches no network and files nothing. Show the person
|
|
1021
|
+
you are working with the whole draft — not a summary of it. They open the
|
|
1022
|
+
URL, read the prefilled form, tick the review boxes, and submit. File only
|
|
1023
|
+
after they say yes.
|
|
1007
1024
|
|
|
1008
1025
|
If they say no, that is the end of it. Do not re-file the same finding in
|
|
1009
1026
|
another form, and do not open it as a question instead.
|
|
@@ -758,8 +758,8 @@ kici-admin org-settings global-workflows deny-add \
|
|
|
758
758
|
```
|
|
759
759
|
|
|
760
760
|
See [Global workflows](https://docs.kici.dev/architecture/global-workflows/) for the
|
|
761
|
-
policy model (`isWorkflowRepoAllowed` + `isSourceRepoAllowed`
|
|
762
|
-
|
|
761
|
+
policy model (`isWorkflowRepoAllowed` + `isSourceRepoAllowed`) and the
|
|
762
|
+
cross-provider dispatch contract.
|
|
763
763
|
|
|
764
764
|
## Routing-key collisions
|
|
765
765
|
|
|
@@ -1332,6 +1332,17 @@ interface StepContext<TInputs = Record<string, unknown>> {
|
|
|
1332
1332
|
provider?: string;
|
|
1333
1333
|
/** Whether this execution was triggered by `kici run remote` (developer-initiated remote run) */
|
|
1334
1334
|
isTestRun: boolean;
|
|
1335
|
+
/** The job's own checked-out repository (present for every job that checks out); `withWrite` opens a write window for it — see [Git credentials](https://docs.kici.dev/user/patterns/git-credentials/) */
|
|
1336
|
+
repo?: {
|
|
1337
|
+
identifier: string;
|
|
1338
|
+
path: string;
|
|
1339
|
+
ref?: string;
|
|
1340
|
+
sha?: string;
|
|
1341
|
+
withWrite(
|
|
1342
|
+
opts: { permissions?: Record<string, string>; credential?: string },
|
|
1343
|
+
fn: () => Promise<void>,
|
|
1344
|
+
): Promise<void>;
|
|
1345
|
+
};
|
|
1335
1346
|
/** Registering repo of a global workflow (undefined for non-global workflows) */
|
|
1336
1347
|
workflowRepo?: RepoInfo;
|
|
1337
1348
|
/** Repo where the triggering event occurred (undefined for non-global workflows) */
|
|
@@ -1532,13 +1543,15 @@ const deploy = job('deploy', {
|
|
|
1532
1543
|
|
|
1533
1544
|
Request a short-lived OIDC ID token for the current job, bound to an `audience`. The token is a signed JWT whose identity claims (`repository`, `ref`, `sha`, `kici_run_id`, `kici_job_id`) are derived by your orchestrator from the run context — a step cannot spoof them. Use it to authenticate the build to an external service that trusts the orchestrator's OIDC issuer (for example, when generating build provenance).
|
|
1534
1545
|
|
|
1535
|
-
The token also carries the event context a cloud trust policy needs to tell a fork pull request from a trusted push — `is_fork`, `head_repository`, `trust_tier`, `event_name`, and a pull-request-specific `sub`. See [ID-token claims and cloud trust policies](https://docs.kici.dev/user/
|
|
1546
|
+
The token also carries the event context a cloud trust policy needs to tell a fork pull request from a trusted push — `is_fork`, `head_repository`, `trust_tier`, `event_name`, and a pull-request-specific `sub`. See [ID-token claims and cloud trust policies](https://docs.kici.dev/user/oidc/#id-token-claims-and-cloud-trust-policies) for the full claim table and a worked AWS policy.
|
|
1536
1547
|
|
|
1537
1548
|
```typescript
|
|
1538
1549
|
const publish = job('publish', {
|
|
1539
1550
|
steps: [
|
|
1540
1551
|
step('mint', async (ctx) => {
|
|
1541
|
-
const
|
|
1552
|
+
const minted = await ctx.kici.oidc.token({ audience: 'sigstore' });
|
|
1553
|
+
if ('deferred' in minted) throw new Error(`ID token deferred: ${minted.code}`);
|
|
1554
|
+
const { token, expiresIn } = minted;
|
|
1542
1555
|
ctx.log.info(`Got an ID token valid for ${expiresIn}s`);
|
|
1543
1556
|
// Hand `token` to a tool that exchanges it with the trusting service.
|
|
1544
1557
|
}),
|
|
@@ -1548,6 +1561,7 @@ const publish = job('publish', {
|
|
|
1548
1561
|
|
|
1549
1562
|
**Behavior:**
|
|
1550
1563
|
|
|
1564
|
+
- The result is the minted token, or `{ deferred: true, code }` (`unavailable` | `failed`) when the orchestrator could not mint one right now. A step that exchanges the token fails on `deferred`; `ctx.attestProvenance` handles it by freezing the statement for later fulfilment.
|
|
1551
1565
|
- The token is short-lived (about 10 minutes) and scoped to the current run and job.
|
|
1552
1566
|
- The returned token value is automatically masked in step logs.
|
|
1553
1567
|
- The step never holds signing credentials — the orchestrator mints and signs the token on the step's behalf from its own run records.
|
|
@@ -1051,9 +1051,8 @@ The throw is a `ChangedFilesUnavailableError` (exported from `@kici-dev/sdk`, ca
|
|
|
1051
1051
|
### evaluateRules(rules, context, label, onRuleResult?)
|
|
1052
1052
|
|
|
1053
1053
|
The agent calls this on your behalf. A workflow does not call it. It lives on
|
|
1054
|
-
`@kici-dev/sdk/internal`, outside semver, and
|
|
1055
|
-
|
|
1056
|
-
because its return shape is what a rule's outcome looks like in the run log.
|
|
1054
|
+
`@kici-dev/sdk/internal`, outside semver, and is not exported from the root barrel. It is
|
|
1055
|
+
described here because its return shape is what a rule's outcome looks like in the run log.
|
|
1057
1056
|
|
|
1058
1057
|
Evaluate an array of rules sequentially with fail-fast behavior. Stops on the first failure.
|
|
1059
1058
|
|
|
@@ -1306,9 +1305,8 @@ isDynamicFunction(matrix); // true if async function
|
|
|
1306
1305
|
### Matrix expansion utilities
|
|
1307
1306
|
|
|
1308
1307
|
The agent expands a matrix for you. A workflow does not call these. They live on
|
|
1309
|
-
`@kici-dev/sdk/internal`, outside semver, and
|
|
1310
|
-
|
|
1311
|
-
here because they define the combinations a matrix job actually produces.
|
|
1308
|
+
`@kici-dev/sdk/internal`, outside semver, and are not exported from the root barrel. They are
|
|
1309
|
+
described here because they define the combinations a matrix job actually produces.
|
|
1312
1310
|
|
|
1313
1311
|
```typescript
|
|
1314
1312
|
import { expandMatrix, applyIncludeExclude } from '@kici-dev/sdk/internal';
|
|
@@ -1905,7 +1903,7 @@ Create a commit status trigger. Returns a frozen `StatusTriggerConfig`.
|
|
|
1905
1903
|
function status(config?: StatusConfigInput): StatusTriggerConfig;
|
|
1906
1904
|
```
|
|
1907
1905
|
|
|
1908
|
-
**Config options:** `contexts` (
|
|
1906
|
+
**Config options:** `contexts` (glob patterns like 'ci/\*'), `states` (error/failure/pending/success), `description`
|
|
1909
1907
|
|
|
1910
1908
|
```typescript
|
|
1911
1909
|
status(); // Any status
|
|
@@ -2531,11 +2529,8 @@ a `.result` proxy reads, build the step context your workflow body receives, eva
|
|
|
2531
2529
|
rules, and expand its matrix. The agent drives all of it on your behalf.
|
|
2532
2530
|
|
|
2533
2531
|
It is **not covered by semver** and may change shape in any release. Do not import it from
|
|
2534
|
-
a workflow. Everything a workflow author needs is on the root entry point above
|
|
2535
|
-
|
|
2536
|
-
Those same symbols are also still exported from the root barrel, marked `@deprecated`, so
|
|
2537
|
-
an older SDK in a repository keeps working. They are removed from the root at v1.0.0 — see
|
|
2538
|
-
[deprecations](https://docs.kici.dev/user/deprecations/).
|
|
2532
|
+
a workflow. Everything a workflow author needs is on the root entry point above, and none
|
|
2533
|
+
of the runtime contract is exported there.
|
|
2539
2534
|
|
|
2540
2535
|
## See also
|
|
2541
2536
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# KiCI
|
|
2
2
|
|
|
3
|
-
> KiCI is a TypeScript-native CI/CD workflow engine. Workflows are defined in TypeScript (not YAML), compiled into a portable lock file, and executed by self-hosted agents. The docs below cover the SDK, the CLI, workflow patterns, and the runtime architecture an LLM coding agent needs
|
|
3
|
+
> KiCI is a TypeScript-native CI/CD workflow engine. Workflows are defined in TypeScript (not YAML), compiled into a portable lock file, and executed by self-hosted agents. The docs below cover the SDK, the CLI, workflow patterns, and the runtime architecture an LLM coding agent needs to author and test KiCI workflows.
|
|
4
4
|
|
|
5
5
|
The full markdown bundle of every page indexed here is available at https://docs.kici.dev/llms-full.txt.
|
|
6
6
|
|
|
@@ -10,16 +10,16 @@ Found a mismatch between what these docs advertise and what KiCI does? Read http
|
|
|
10
10
|
|
|
11
11
|
Each bundle below is a self-contained markdown file for one authoring task. Fetch only the one your task needs instead of the full bundle:
|
|
12
12
|
|
|
13
|
-
- [getting-started](https://docs.kici.dev/llms-getting-started.txt) (
|
|
13
|
+
- [getting-started](https://docs.kici.dev/llms-getting-started.txt) (66 KB) — Adopt KiCI: why it exists, how workflows execute, migrating from GitHub Actions, installing the SDK, and writing/compiling/testing your first workflow
|
|
14
14
|
- [patterns](https://docs.kici.dev/llms-patterns.txt) (61 KB) — Copy-paste workflow recipes: triggers, conditionals, matrix, scheduling, integrations
|
|
15
15
|
- [sdk](https://docs.kici.dev/llms-sdk.txt) (131 KB) — Core authoring API: workflow/job/step factories, triggers, rules, matrix, dynamic jobs, cross-job outputs
|
|
16
|
-
- [sdk-runtime](https://docs.kici.dev/llms-sdk-runtime.txt) (
|
|
16
|
+
- [sdk-runtime](https://docs.kici.dev/llms-sdk-runtime.txt) (116 KB) — Runtime and advanced authoring: step runtime context, event payloads, host fan-out, idempotent steps, caching, artifacts, waiting
|
|
17
17
|
- [cli](https://docs.kici.dev/llms-cli.txt) (97 KB) — Running the CLI locally: compile, test, run local, hooks, lock-file drift, common failures
|
|
18
|
-
- [cli-remote](https://docs.kici.dev/llms-cli-remote.txt) (
|
|
19
|
-
- [features](https://docs.kici.dev/llms-features.txt) (
|
|
18
|
+
- [cli-remote](https://docs.kici.dev/llms-cli-remote.txt) (111 KB) — Auth, org and orchestrator selection, runs, approvals, notifications, diagnostics, and the MCP server a coding agent connects to
|
|
19
|
+
- [features](https://docs.kici.dev/llms-features.txt) (112 KB) — Deployment contexts, scoped secrets, private registries, approvals, workload identity, provenance, dashboard and account access
|
|
20
20
|
- [features-execution](https://docs.kici.dev/llms-features-execution.txt) (124 KB) — Concurrency, dynamic values, events, container jobs, environment variables, global workflows, idempotent steps
|
|
21
21
|
- [providers](https://docs.kici.dev/llms-providers.txt) (35 KB) — Connecting sources: GitHub App, universal-git (Forgejo/Gitea/GitLab), local file://
|
|
22
|
-
- [architecture](https://docs.kici.dev/llms-architecture.txt) (
|
|
22
|
+
- [architecture](https://docs.kici.dev/llms-architecture.txt) (101 KB) — How the runtime works: three-tier relay model, data flows, configuration
|
|
23
23
|
|
|
24
24
|
## Getting started
|
|
25
25
|
|
|
@@ -86,6 +86,7 @@ Each bundle below is a self-contained markdown file for one authoring task. Fetc
|
|
|
86
86
|
- [Approval gates](https://docs.kici.dev/user/approvals/): Pause a workflow for human sign-off at step, job, or workflow granularity with approval
|
|
87
87
|
- [Contexts](https://docs.kici.dev/user/contexts/): Configure deployment contexts with variables, secrets, and protection rules
|
|
88
88
|
- [Dashboard](https://docs.kici.dev/user/dashboard/): Web UI for monitoring workflow runs, managing sources, secrets, and organization settings.
|
|
89
|
+
- [Workload identity with OIDC](https://docs.kici.dev/user/oidc/): Mint short-lived OIDC identity tokens in a step and exchange them with Cloudsmith, AWS, or any service that federates with your orchestrator
|
|
89
90
|
- [Private npm registries](https://docs.kici.dev/user/private-registries/): Authenticate `npm install` against private registries (CodeArtifact, GitHub Packages, Verdaccio, …) from a workflow's `.kici/package.json`
|
|
90
91
|
- [Build provenance and attestations](https://docs.kici.dev/user/provenance/): Generate and verify signed SLSA provenance for the artifacts your workflows build
|
|
91
92
|
- [Secrets](https://docs.kici.dev/user/secrets/): How to access secrets in KiCI workflow steps
|
|
@@ -26,15 +26,14 @@ export interface SpawnOrchestratorOptions {
|
|
|
26
26
|
* generated by the plane. Enables the orchestrator's local OIDC mint +
|
|
27
27
|
* dev-signed provenance (issuer `kici-local`) in independent mode. Present in
|
|
28
28
|
* the independent boot; omitted (with `attach`) in the hybrid boot, which
|
|
29
|
-
* mints
|
|
29
|
+
* mints with the orchestrator's own signing key instead.
|
|
30
30
|
*/
|
|
31
31
|
devIdentityKeyFile?: string;
|
|
32
32
|
/**
|
|
33
33
|
* When present, boot the orchestrator in HYBRID mode attached to the Platform
|
|
34
|
-
* relay instead of independent mode. OIDC + attestation then mint
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* connection exists).
|
|
34
|
+
* relay instead of independent mode. OIDC + attestation then mint with the
|
|
35
|
+
* orchestrator's own signing key under its own issuer, exactly as a deployed
|
|
36
|
+
* orchestrator does (the local dev signer is never consulted).
|
|
38
37
|
*/
|
|
39
38
|
attach?: {
|
|
40
39
|
/** Orchestrator KICI_PLATFORM_URL — a ws(s)://…/ws relay URL. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
2
|
import { planePaths, planePorts } from "./paths.js";
|
|
3
|
+
import { rotatePlaneLogIfOversized } from "./plane-log.js";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import fs from "node:fs";
|
|
5
6
|
import { spawn } from "node:child_process";
|
|
@@ -48,6 +49,7 @@ function spawnOrchestratorProcess(databaseUrl, opts) {
|
|
|
48
49
|
const { logFile, root, cacheDir } = planePaths();
|
|
49
50
|
fs.mkdirSync(root, { recursive: true });
|
|
50
51
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
52
|
+
rotatePlaneLogIfOversized(logFile);
|
|
51
53
|
const out = fs.openSync(logFile, "a");
|
|
52
54
|
const baseEnv = {};
|
|
53
55
|
for (const [key, value] of Object.entries(process.env)) if (!key.startsWith("KICI_")) baseEnv[key] = value;
|
|
@@ -58,6 +60,7 @@ function spawnOrchestratorProcess(databaseUrl, opts) {
|
|
|
58
60
|
KICI_AGENT_AUTH: "none",
|
|
59
61
|
KICI_HOST: "127.0.0.1",
|
|
60
62
|
KICI_AUTO_MIGRATE: "true",
|
|
63
|
+
KICI_CLUSTER_SINGLE_NODE: "true",
|
|
61
64
|
KICI_DATA_DIR: root,
|
|
62
65
|
KICI_BOOTSTRAP_ADMIN_TOKEN: opts.adminToken,
|
|
63
66
|
KICI_SECRET_KEY: opts.secretKey,
|
|
@@ -74,7 +77,8 @@ function spawnOrchestratorProcess(databaseUrl, opts) {
|
|
|
74
77
|
modeEnv = {
|
|
75
78
|
KICI_MODE: "hybrid",
|
|
76
79
|
KICI_PLATFORM_URL: opts.attach.platformWsUrl,
|
|
77
|
-
KICI_PLATFORM_TOKEN: opts.attach.platformToken
|
|
80
|
+
KICI_PLATFORM_TOKEN: opts.attach.platformToken,
|
|
81
|
+
KICI_ORCHESTRATOR_PROVENANCE_ISSUER: `http://127.0.0.1:${port}`
|
|
78
82
|
};
|
|
79
83
|
} else {
|
|
80
84
|
entry = resolveStandaloneEntry();
|
|
@@ -48,6 +48,7 @@ function planePaths() {
|
|
|
48
48
|
pidfile: path.join(root, "plane.pid"),
|
|
49
49
|
stampFile: path.join(root, "stamp.json"),
|
|
50
50
|
logFile: path.join(root, "orchestrator.log"),
|
|
51
|
+
pgLogFile: path.join(root, "orchestrator.log.pg"),
|
|
51
52
|
socketDir: path.join(root, "sock"),
|
|
52
53
|
adminTokenFile: path.join(root, "admin-token"),
|
|
53
54
|
platformTokenFile: path.join(root, "platform-token"),
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Size at which a local dev plane log is rotated on the next plane start.
|
|
3
|
+
*
|
|
4
|
+
* The plane's orchestrator writes one JSON line per request and per
|
|
5
|
+
* scheduled-job tick, and nothing truncates the file between boots, so an
|
|
6
|
+
* unrotated log grows without bound. 50 MB is roughly 200k lines — more history
|
|
7
|
+
* than a local debugging session reads.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PLANE_LOG_MAX_BYTES: number;
|
|
10
|
+
/** The cap expressed in whole megabytes, for the messages that name it. */
|
|
11
|
+
export declare const PLANE_LOG_MAX_MB: number;
|
|
12
|
+
/**
|
|
13
|
+
* Rotate `file` to `<file>.1` once it reaches `PLANE_LOG_MAX_BYTES`, keeping two
|
|
14
|
+
* generations: any previous `.1` is discarded.
|
|
15
|
+
*
|
|
16
|
+
* Call this only where nothing holds the file open. A rename under a live
|
|
17
|
+
* writer leaves that writer appending to the renamed inode, so the live log
|
|
18
|
+
* would stay empty for the rest of the process's life.
|
|
19
|
+
*
|
|
20
|
+
* Every failure is swallowed on purpose. Both callers are on a plane-boot path:
|
|
21
|
+
* `startPlanePostgres` reads a throw as "embedded Postgres is unavailable" and
|
|
22
|
+
* silently falls back to a Podman container, and `spawnOrchestratorProcess`
|
|
23
|
+
* would fail the boot outright. A log that cannot be rotated must not cost the
|
|
24
|
+
* user their plane.
|
|
25
|
+
*/
|
|
26
|
+
export declare function rotatePlaneLogIfOversized(file: string): void;
|
|
27
|
+
//# sourceMappingURL=plane-log.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
//#region src/local-plane/plane-log.ts
|
|
4
|
+
/**
|
|
5
|
+
* Size at which a local dev plane log is rotated on the next plane start.
|
|
6
|
+
*
|
|
7
|
+
* The plane's orchestrator writes one JSON line per request and per
|
|
8
|
+
* scheduled-job tick, and nothing truncates the file between boots, so an
|
|
9
|
+
* unrotated log grows without bound. 50 MB is roughly 200k lines — more history
|
|
10
|
+
* than a local debugging session reads.
|
|
11
|
+
*/
|
|
12
|
+
const PLANE_LOG_MAX_BYTES = 52428800;
|
|
13
|
+
/** The cap expressed in whole megabytes, for the messages that name it. */
|
|
14
|
+
const PLANE_LOG_MAX_MB = PLANE_LOG_MAX_BYTES / 1048576;
|
|
15
|
+
/**
|
|
16
|
+
* Rotate `file` to `<file>.1` once it reaches `PLANE_LOG_MAX_BYTES`, keeping two
|
|
17
|
+
* generations: any previous `.1` is discarded.
|
|
18
|
+
*
|
|
19
|
+
* Call this only where nothing holds the file open. A rename under a live
|
|
20
|
+
* writer leaves that writer appending to the renamed inode, so the live log
|
|
21
|
+
* would stay empty for the rest of the process's life.
|
|
22
|
+
*
|
|
23
|
+
* Every failure is swallowed on purpose. Both callers are on a plane-boot path:
|
|
24
|
+
* `startPlanePostgres` reads a throw as "embedded Postgres is unavailable" and
|
|
25
|
+
* silently falls back to a Podman container, and `spawnOrchestratorProcess`
|
|
26
|
+
* would fail the boot outright. A log that cannot be rotated must not cost the
|
|
27
|
+
* user their plane.
|
|
28
|
+
*/
|
|
29
|
+
function rotatePlaneLogIfOversized(file) {
|
|
30
|
+
try {
|
|
31
|
+
if (fs.statSync(file).size < 52428800) return;
|
|
32
|
+
fs.rmSync(`${file}.1`, { force: true });
|
|
33
|
+
fs.renameSync(file, `${file}.1`);
|
|
34
|
+
} catch {}
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { PLANE_LOG_MAX_BYTES, PLANE_LOG_MAX_MB, rotatePlaneLogIfOversized };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=plane-log.js.map
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* The source's bundle hot-reload is debounced, so a first trigger can land
|
|
12
12
|
* before the plane has registered the (re-pointed) local source. This resends
|
|
13
13
|
* the webhook after a grace window until a run appears or the timeout elapses.
|
|
14
|
+
* When the budget still runs out, the timeout is explained rather than merely
|
|
15
|
+
* reported: the plane's own Raft role and its event-log record for this
|
|
16
|
+
* delivery decide which cause is named.
|
|
14
17
|
*/
|
|
15
18
|
/** A minimal admin-read client (AdminApiClient.get) — injectable for tests. */
|
|
16
19
|
export interface RunDiscoveryClient {
|
|
@@ -58,7 +61,32 @@ export interface TriggerRunOptions {
|
|
|
58
61
|
pollIntervalMs?: number;
|
|
59
62
|
resendAfterMs?: number;
|
|
60
63
|
timeoutMs?: number;
|
|
64
|
+
/** Workdir the plane's local source points at — named in the lock-file diagnosis. */
|
|
65
|
+
repoBasePath?: string;
|
|
66
|
+
/** Plane log path — named when nothing else explains the timeout. */
|
|
67
|
+
logPath?: string;
|
|
61
68
|
}
|
|
69
|
+
/** What the diagnosis knows about the trigger that just timed out. */
|
|
70
|
+
export interface TriggerTimeoutContext {
|
|
71
|
+
/** Routing-key-scoped delivery id, or null when the plane never accepted the webhook. */
|
|
72
|
+
deliveryId: string | null;
|
|
73
|
+
orgId: string;
|
|
74
|
+
/** Commit the synthetic push carried (the workdir's overlay commit). */
|
|
75
|
+
sha: string;
|
|
76
|
+
/** Absolute path the plane's local source points at — the run's workdir. */
|
|
77
|
+
repoBasePath?: string;
|
|
78
|
+
/** Plane log path, named in the fallback so the developer has somewhere to look. */
|
|
79
|
+
logPath?: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Explain a trigger timeout in one line, from what the plane itself recorded.
|
|
83
|
+
*
|
|
84
|
+
* Ordered most-specific-first, and every branch is a fact read back off the
|
|
85
|
+
* plane rather than an inference: the Raft role it reports, then the status it
|
|
86
|
+
* wrote for THIS delivery, then the delivery id + log path so the developer has
|
|
87
|
+
* a thread to pull even when neither surface answered.
|
|
88
|
+
*/
|
|
89
|
+
export declare function diagnoseTriggerTimeout(client: RunDiscoveryClient, ctx: TriggerTimeoutContext): Promise<string>;
|
|
62
90
|
/**
|
|
63
91
|
* Trigger the run and resolve its runId. Sends the synthetic push, then polls
|
|
64
92
|
* the admin runs list filtered by this webhook's routing-key-scoped delivery id
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { EventLogStatus } from "@kici-dev/engine";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { AdminApiClient } from "@kici-dev/orchestrator";
|
|
4
5
|
//#region src/local-plane/plane-trigger.ts
|
|
@@ -15,6 +16,9 @@ import { AdminApiClient } from "@kici-dev/orchestrator";
|
|
|
15
16
|
* The source's bundle hot-reload is debounced, so a first trigger can land
|
|
16
17
|
* before the plane has registered the (re-pointed) local source. This resends
|
|
17
18
|
* the webhook after a grace window until a run appears or the timeout elapses.
|
|
19
|
+
* When the budget still runs out, the timeout is explained rather than merely
|
|
20
|
+
* reported: the plane's own Raft role and its event-log record for this
|
|
21
|
+
* delivery decide which cause is named.
|
|
18
22
|
*/
|
|
19
23
|
/**
|
|
20
24
|
* Build the GitHub-shaped webhook request the plane's local provider normalizer
|
|
@@ -62,6 +66,50 @@ async function sendLocalTrigger(planeUrl, req) {
|
|
|
62
66
|
deliveryId
|
|
63
67
|
};
|
|
64
68
|
}
|
|
69
|
+
/** Raft role the plane reports, or null when `/cluster/health` cannot be read. */
|
|
70
|
+
async function readPlaneRole(client) {
|
|
71
|
+
try {
|
|
72
|
+
const health = await client.get("/cluster/health");
|
|
73
|
+
return typeof health.role === "string" ? health.role : null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** The plane's own record of this delivery, or null when there is none to read. */
|
|
79
|
+
async function readDelivery(client, orgId, deliveryId) {
|
|
80
|
+
try {
|
|
81
|
+
const qs = new URLSearchParams({
|
|
82
|
+
deliveryId,
|
|
83
|
+
orgId,
|
|
84
|
+
limit: "1"
|
|
85
|
+
});
|
|
86
|
+
return (await client.get(`/api/v1/admin/event-log?${qs}`)).deliveries?.[0] ?? null;
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Explain a trigger timeout in one line, from what the plane itself recorded.
|
|
93
|
+
*
|
|
94
|
+
* Ordered most-specific-first, and every branch is a fact read back off the
|
|
95
|
+
* plane rather than an inference: the Raft role it reports, then the status it
|
|
96
|
+
* wrote for THIS delivery, then the delivery id + log path so the developer has
|
|
97
|
+
* a thread to pull even when neither surface answered.
|
|
98
|
+
*/
|
|
99
|
+
async function diagnoseTriggerTimeout(client, ctx) {
|
|
100
|
+
const role = await readPlaneRole(client);
|
|
101
|
+
if (role !== null && role !== "leader") return `plane is not leader yet (election grace period) — it reports Raft role "${role}". A single-node plane should elect itself within seconds; check ${ctx.logPath ?? "the plane log (`kici local logs`)"} for "self-electing as leader".`;
|
|
102
|
+
const delivery = ctx.deliveryId ? await readDelivery(client, ctx.orgId, ctx.deliveryId) : null;
|
|
103
|
+
if (delivery?.status === EventLogStatus.enum.lockfile_missing) {
|
|
104
|
+
const where = ctx.repoBasePath ? ` in ${ctx.repoBasePath}` : "";
|
|
105
|
+
return `no kici.lock.json at ${ctx.sha}${where} — the plane resolved no lock file for this commit, so nothing matched. Make sure .kici/kici.lock.json is committed or present in the working tree the run packs.`;
|
|
106
|
+
}
|
|
107
|
+
if (delivery?.status) {
|
|
108
|
+
const detail = delivery.errorMessage ? ` (${delivery.errorMessage})` : "";
|
|
109
|
+
return `the plane recorded delivery ${ctx.deliveryId} as "${delivery.status}"${detail} but created no run.`;
|
|
110
|
+
}
|
|
111
|
+
return `no run appeared for ${ctx.deliveryId ? `delivery ${ctx.deliveryId}` : "this trigger — the plane never accepted the webhook (no delivery id came back)"}${ctx.logPath ? ` — see ${ctx.logPath}` : ""}`;
|
|
112
|
+
}
|
|
65
113
|
/**
|
|
66
114
|
* Trigger the run and resolve its runId. Sends the synthetic push, then polls
|
|
67
115
|
* the admin runs list filtered by this webhook's routing-key-scoped delivery id
|
|
@@ -90,7 +138,14 @@ async function triggerRun(planeUrl, adminToken, input, opts = {}) {
|
|
|
90
138
|
}
|
|
91
139
|
await sleep(pollIntervalMs);
|
|
92
140
|
}
|
|
93
|
-
|
|
141
|
+
const cause = await diagnoseTriggerTimeout(client, {
|
|
142
|
+
deliveryId,
|
|
143
|
+
orgId: input.orgId,
|
|
144
|
+
sha: input.sha,
|
|
145
|
+
...opts.repoBasePath !== void 0 && { repoBasePath: opts.repoBasePath },
|
|
146
|
+
...opts.logPath !== void 0 && { logPath: opts.logPath }
|
|
147
|
+
});
|
|
148
|
+
throw new Error(`offline run: ${cause}`);
|
|
94
149
|
}
|
|
95
150
|
/** Return the run created by this webhook delivery, or null when none yet. */
|
|
96
151
|
async function findRunByDelivery(client, deliveryId) {
|
|
@@ -105,6 +160,6 @@ function sleep(ms) {
|
|
|
105
160
|
return new Promise((r) => setTimeout(r, ms));
|
|
106
161
|
}
|
|
107
162
|
//#endregion
|
|
108
|
-
export { buildLocalTriggerRequest, sendLocalTrigger, triggerRun };
|
|
163
|
+
export { buildLocalTriggerRequest, diagnoseTriggerTimeout, sendLocalTrigger, triggerRun };
|
|
109
164
|
|
|
110
165
|
//# sourceMappingURL=plane-trigger.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
2
|
import { planePaths, planePorts } from "./paths.js";
|
|
3
|
+
import { rotatePlaneLogIfOversized } from "./plane-log.js";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import fs from "node:fs";
|
|
@@ -104,14 +105,13 @@ async function embeddedClusterIsServing(port) {
|
|
|
104
105
|
/**
|
|
105
106
|
* Start a detached embedded postmaster via `pg_ctl` so it survives the exit of
|
|
106
107
|
* this CLI process (embedded-postgres's in-process server is killed by its own
|
|
107
|
-
* exit hook, so it cannot back a warm plane).
|
|
108
|
-
*
|
|
108
|
+
* exit hook, so it cannot back a warm plane). The caller decides whether a
|
|
109
|
+
* cluster is already serving; this always starts one.
|
|
109
110
|
*/
|
|
110
111
|
async function defaultEmbeddedDaemon(port) {
|
|
111
|
-
const { pgData,
|
|
112
|
-
if (await embeddedClusterIsServing(port)) return;
|
|
112
|
+
const { pgData, pgLogFile } = planePaths();
|
|
113
113
|
const pgCtl = resolvePgCtl();
|
|
114
|
-
await $`${pgCtl} -D ${pgData} -o ${`-p ${port}`} -l ${
|
|
114
|
+
await $`${pgCtl} -D ${pgData} -o ${`-p ${port}`} -l ${pgLogFile} -w start`.quiet();
|
|
115
115
|
}
|
|
116
116
|
/** Stop the detached embedded postmaster (handle-independent, reads the data dir). */
|
|
117
117
|
async function stopEmbeddedDaemon() {
|
|
@@ -141,7 +141,10 @@ async function startPlanePostgres(opts = {}) {
|
|
|
141
141
|
const url = `postgres://kici:kici@127.0.0.1:${port}/kici_local`;
|
|
142
142
|
if (!(opts.forcePodman || process.env.KICI_LOCAL_PG_MODE === "podman")) try {
|
|
143
143
|
await ensureEmbeddedCluster(port);
|
|
144
|
-
await
|
|
144
|
+
if (!await embeddedClusterIsServing(port)) {
|
|
145
|
+
rotatePlaneLogIfOversized(planePaths().pgLogFile);
|
|
146
|
+
await embeddedDaemon(port);
|
|
147
|
+
}
|
|
145
148
|
return {
|
|
146
149
|
url,
|
|
147
150
|
kind: "embedded",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { unwrapStoredLogLine } from "../remote/output/streaming.js";
|
|
2
3
|
import { ExecutionJobStatus, ExecutionRunStatus, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES } from "@kici-dev/engine";
|
|
3
4
|
import { AdminApiClient } from "@kici-dev/orchestrator";
|
|
4
5
|
//#region src/local-plane/run-follow.ts
|
|
@@ -131,7 +132,7 @@ async function drainLogs(client, runId, cursors, onLine) {
|
|
|
131
132
|
const qs = cursor ? `?cursor=${encodeURIComponent(cursor)}` : "";
|
|
132
133
|
const page = await client.get(`/api/v1/admin/runs/${runId}/jobs/${job.jobId}/steps/${step.stepIndex}/logs${qs}`);
|
|
133
134
|
for (const l of page.lines) {
|
|
134
|
-
onLine(l.value);
|
|
135
|
+
onLine(unwrapStoredLogLine(l.value));
|
|
135
136
|
emitted++;
|
|
136
137
|
}
|
|
137
138
|
cursor = page.nextCursor ?? String(page.totalLines);
|
|
@@ -7,6 +7,18 @@
|
|
|
7
7
|
* - Prints step transition headers
|
|
8
8
|
* - Tracks elapsed time with in-place status updates
|
|
9
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* The text a stored log line carries.
|
|
12
|
+
*
|
|
13
|
+
* The orchestrator stores every step log line as a JSON envelope —
|
|
14
|
+
* `{"ts":…,"level":"stdout","msg":"…","meta":{}}` — and the Platform relay
|
|
15
|
+
* returns those envelopes verbatim, so a run's log stream is the envelope
|
|
16
|
+
* stream. The dashboard unwraps `msg` before rendering; the terminal must too,
|
|
17
|
+
* or the developer watching `kici run remote` reads raw JSON. A line that is
|
|
18
|
+
* not an envelope (an orchestrator phase marker, a plain line from an older
|
|
19
|
+
* store) passes through unchanged.
|
|
20
|
+
*/
|
|
21
|
+
export declare function unwrapStoredLogLine(line: string): string;
|
|
10
22
|
export declare class StreamingFormatter {
|
|
11
23
|
/** Color assignment per job name. */
|
|
12
24
|
private readonly jobColors;
|