@kici-dev/compiler 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/commands/compile.js +1 -13
  3. package/dist/commands/preview.js +1 -8
  4. package/dist/commands/types.js +1 -2
  5. package/dist/errors/formatter.d.ts +2 -4
  6. package/dist/errors/formatter.js +1 -3
  7. package/dist/errors/index.d.ts +1 -1
  8. package/dist/errors/index.js +2 -2
  9. package/dist/generators/secrets-dts.d.ts +0 -1
  10. package/dist/generators/secrets-dts.js +1 -2
  11. package/dist/llm-context/llms-architecture.txt +27 -13
  12. package/dist/llm-context/llms-cli.txt +26 -6
  13. package/dist/llm-context/llms-features.txt +272 -91
  14. package/dist/llm-context/llms-full.txt +649 -235
  15. package/dist/llm-context/llms-getting-started.txt +20 -20
  16. package/dist/llm-context/llms-patterns.txt +10 -6
  17. package/dist/llm-context/llms-providers.txt +4 -6
  18. package/dist/llm-context/llms-sdk-runtime.txt +37 -36
  19. package/dist/llm-context/llms-sdk.txt +253 -57
  20. package/dist/llm-context/llms.txt +6 -6
  21. package/dist/local-plane/plane-manager.js +2 -2
  22. package/dist/lockfile/generator.js +137 -42
  23. package/dist/lockfile/index.d.ts +0 -2
  24. package/dist/lockfile/index.js +1 -2
  25. package/dist/templates/package-json.js +1 -1
  26. package/dist/test-runner/dry-run.d.ts +1 -2
  27. package/dist/test-runner/dry-run.js +1 -18
  28. package/dist/types.d.ts +32 -8
  29. package/dist/types.js +7 -1
  30. package/dist/validation/validator.js +40 -0
  31. package/package.json +6 -6
  32. package/sbom.spdx.json +126 -121
  33. package/dist/lockfile/purity-analyzer.d.ts +0 -25
  34. package/dist/lockfile/purity-analyzer.js +0 -204
  35. package/dist/lockfile/purity-diagnostics.d.ts +0 -31
  36. package/dist/lockfile/purity-diagnostics.js +0 -52
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { realpathSync } from "node:fs";
7
7
  import { Argument, Command, Option } from "commander";
8
8
  import pc from "picocolors";
9
9
  //#region src/cli.ts
10
- const version = "0.4.0";
10
+ const version = "0.5.0";
11
11
  /**
12
12
  * Top-level commands that were removed, mapped to their current equivalent.
13
13
  * Consulted when the CLI hits an unknown command so the user gets a precise
@@ -1,5 +1,5 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
- import { PURITY_FALLBACK_CODE, compilerError, formatError, isCompilerError } from "../errors/formatter.js";
2
+ import { formatError, isCompilerError } from "../errors/formatter.js";
3
3
  import "../errors/index.js";
4
4
  import { discoverWorkflows, resolveKiciDir } from "../execution/executor.js";
5
5
  import "../execution/index.js";
@@ -8,7 +8,6 @@ import { runTypecheck } from "../validation/typecheck.js";
8
8
  import "../validation/index.js";
9
9
  import { BREAKING_FLOOR, SCHEMA_VERSION } from "../types.js";
10
10
  import { computeLockfileHash, detectGitRoot, generateLockFile, schemaWindowWarning, serializeLockFile } from "../lockfile/generator.js";
11
- import { collectWorkflowPurityWarnings } from "../lockfile/purity-diagnostics.js";
12
11
  import "../lockfile/index.js";
13
12
  import path from "node:path";
14
13
  import { existsSync } from "node:fs";
@@ -19,16 +18,6 @@ import { PackageManager, detectPackageManagerSync } from "@kici-dev/core/package
19
18
  import { execSync } from "node:child_process";
20
19
  //#region src/commands/compile.ts
21
20
  /**
22
- * Render an impure dynamic-value function as a `W101` compile warning naming the
23
- * job, the field, the impurity reason, and the ~5-10s agent-side init-job cost.
24
- */
25
- function formatPurityWarning(w) {
26
- return formatError(compilerError(PURITY_FALLBACK_CODE, `job "${w.jobName}": ${w.field} function is not pure (${w.reason}). An init job will be required, adding ~5-10s delay.`, {
27
- severity: "warning",
28
- suggestion: `Make the ${w.field} function pure (synchronous, referencing only its parameters) to inline it and skip the init job. See https://docs.kici.dev — dynamic values.`
29
- }));
30
- }
31
- /**
32
21
  * Read the existing lock file and return its lockfileHash, if present.
33
22
  */
34
23
  async function readExistingLockfileHash(lockPath) {
@@ -87,7 +76,6 @@ async function compileCommand(options) {
87
76
  }
88
77
  if (options.verbose) logger.debug(pc.dim("Validation passed"));
89
78
  const lockJson = serializeLockFile(generateLockFile(workflowsWithSource));
90
- for (const warning of collectWorkflowPurityWarnings(workflowsWithSource)) logger.warn(formatPurityWarning(warning));
91
79
  const windowWarning = schemaWindowWarning(BREAKING_FLOOR, SCHEMA_VERSION);
92
80
  if (windowWarning) logger.warn(pc.yellow(windowWarning));
93
81
  if (!options.check) {
@@ -2,8 +2,6 @@ import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { discoverWorkflows, resolveKiciDir } from "../execution/executor.js";
3
3
  import "../execution/index.js";
4
4
  import { transformTriggers } from "../lockfile/generator.js";
5
- import { analyzeJobPurity } from "../lockfile/purity-diagnostics.js";
6
- import "../lockfile/index.js";
7
5
  import { displayDryRun } from "../test-runner/dry-run.js";
8
6
  import { parseEventArg } from "../test-runner/event-types.js";
9
7
  import { buildEventPayload } from "../test-runner/payload-builder.js";
@@ -90,15 +88,10 @@ async function previewEvent(event, options) {
90
88
  return false;
91
89
  }
92
90
  }
93
- const purityWarnings = [];
94
- for (const w of workflows) for (const j of w.jobs) {
95
- if (typeof j === "function") continue;
96
- purityWarnings.push(...analyzeJobPurity(j, w.name));
97
- }
98
91
  displayDryRun(lockWorkflows, decisions, {
99
92
  workflow: options.workflow,
100
93
  job: options.job
101
- }, purityWarnings);
94
+ });
102
95
  return true;
103
96
  } catch (error) {
104
97
  const message = toErrorMessage(error);
@@ -25,8 +25,7 @@ async function typesCommand(options = {}) {
25
25
  name: e.name,
26
26
  keys: e.secretKeys ?? []
27
27
  })),
28
- endpoint: (config.platformEndpoint ?? config.endpoint ?? "kici Platform").replace(/\/+$/, ""),
29
- generatedAt: /* @__PURE__ */ new Date()
28
+ endpoint: (config.platformEndpoint ?? config.endpoint ?? "kici Platform").replace(/\/+$/, "")
30
29
  });
31
30
  const kiciDir = options.kiciDir ?? ".kici";
32
31
  const typesDir = path.join(kiciDir, "types");
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Error code string. Thrown compile errors use `E<digits>` (e.g. 'E001', 'E102');
3
- * advisory, render-only diagnostics use `W<digits>` (e.g. 'W101'). `isCompilerError`
4
- * only matches the `E<digits>` family — warning codes are never thrown, only rendered.
3
+ * advisory, render-only diagnostics use `W<digits>`. `isCompilerError` only
4
+ * matches the `E<digits>` family — warning codes are never thrown, only rendered.
5
5
  */
6
6
  type ErrorCode = string;
7
7
  /** Diagnostic severity. Errors abort compilation; warnings are advisory (compile still succeeds). */
@@ -9,8 +9,6 @@ export declare enum DiagnosticSeverity {
9
9
  Error = "error",
10
10
  Warning = "warning"
11
11
  }
12
- /** Warning code for the impure dynamic-value → init-job fallback. */
13
- export declare const PURITY_FALLBACK_CODE = "W101";
14
12
  /** Source location for error reporting */
15
13
  export interface SourceLocation {
16
14
  readonly file: string;
@@ -7,8 +7,6 @@ let DiagnosticSeverity = /* @__PURE__ */ function(DiagnosticSeverity) {
7
7
  DiagnosticSeverity["Warning"] = "warning";
8
8
  return DiagnosticSeverity;
9
9
  }({});
10
- /** Warning code for the impure dynamic-value → init-job fallback. */
11
- const PURITY_FALLBACK_CODE = "W101";
12
10
  /**
13
11
  * Format error in GNU standard format: file:line:column: error [CODE]: message
14
12
  *
@@ -40,6 +38,6 @@ function isCompilerError(error) {
40
38
  return typeof error === "object" && error !== null && "code" in error && "message" in error && typeof error.code === "string" && /^E\d+$/.test(error.code);
41
39
  }
42
40
  //#endregion
43
- export { DiagnosticSeverity, PURITY_FALLBACK_CODE, compilerError, formatError, isCompilerError };
41
+ export { DiagnosticSeverity, compilerError, formatError, isCompilerError };
44
42
 
45
43
  //# sourceMappingURL=formatter.js.map
@@ -1,4 +1,4 @@
1
- export { formatError, compilerError, isCompilerError, DiagnosticSeverity, PURITY_FALLBACK_CODE, } from './formatter.js';
1
+ export { formatError, compilerError, isCompilerError, DiagnosticSeverity } from './formatter.js';
2
2
  export type { SourceLocation, CompilerError } from './formatter.js';
3
3
  export { CapabilityGapError, formatCapabilityGapError } from './capability-gap.js';
4
4
  export type { CapabilityGapInfo } from './capability-gap.js';
@@ -1,5 +1,5 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
- import { DiagnosticSeverity, PURITY_FALLBACK_CODE, compilerError, formatError, isCompilerError } from "./formatter.js";
2
+ import { DiagnosticSeverity, compilerError, formatError, isCompilerError } from "./formatter.js";
3
3
  import { CapabilityGapError, formatCapabilityGapError } from "./capability-gap.js";
4
4
  import { firstStepLocation, locationForJob, locationForWorkflow } from "./source-location.js";
5
- export { CapabilityGapError, DiagnosticSeverity, PURITY_FALLBACK_CODE, compilerError, firstStepLocation, formatCapabilityGapError, formatError, isCompilerError, locationForJob, locationForWorkflow };
5
+ export { CapabilityGapError, DiagnosticSeverity, compilerError, firstStepLocation, formatCapabilityGapError, formatError, isCompilerError, locationForJob, locationForWorkflow };
@@ -13,7 +13,6 @@ export interface ContextMetadata {
13
13
  interface GenerateSecretsDtsOptions {
14
14
  contexts: ContextMetadata[];
15
15
  endpoint: string;
16
- generatedAt: Date;
17
16
  }
18
17
  /**
19
18
  * Generate a .d.ts string from context metadata.
@@ -34,11 +34,10 @@ function buildKeySourceMap(contexts) {
34
34
  * @returns The .d.ts file content as a string
35
35
  */
36
36
  function generateSecretsDts(options) {
37
- const { contexts, endpoint, generatedAt } = options;
37
+ const { contexts, endpoint } = options;
38
38
  const lines = [];
39
39
  lines.push("// @generated by kici types -- DO NOT EDIT");
40
40
  lines.push(`// Source: ${endpoint}`);
41
- lines.push(`// Generated: ${generatedAt.toISOString()}`);
42
41
  lines.push("// Run `kici types` to refresh");
43
42
  lines.push("");
44
43
  lines.push("declare module '@kici-dev/sdk' {");
@@ -313,12 +313,13 @@ The `ProviderRegistry` maps routing keys to provider bundles. Each routing key (
313
313
  - `WebhookNormalizer` (required) -- normalizes incoming webhooks to a standard format
314
314
  - `LockFileFetcher` -- fetches lock files from the repository
315
315
  - `ChangedFilesFetcher` -- determines which files changed
316
+ - `FileContentsFetcher` -- reads arbitrary repository files at a ref, for the declarative content-requirements (`requires`) filter
316
317
  - `CloneTokenProvider` -- generates clone tokens for agents
317
318
  - `RepoUrlBuilder` -- builds clone URLs and raw file URLs
318
319
  - `ContributorResolver` -- resolves contributor permissions for trust-tier gating
319
320
  - `CheckStatusPoster` -- posts check statuses (approval/hold) to the git provider
320
321
 
321
- A GitHub App source populates all seven. A plain generic webhook source carries only the normalizer -- it has no repository API to fetch a lock file, resolve a contributor, or post a check against -- so the pipeline skips the stages whose interface is absent rather than failing the delivery.
322
+ A GitHub App source populates all eight -- though the file-contents capability arrives as a per-delivery factory rather than a prebuilt instance, because a GitHub client is scoped to one installation and the installation id is only known once the delivery's credentials are resolved. A plain generic webhook source carries only the normalizer -- it has no repository API to fetch a lock file, resolve a contributor, or post a check against -- so the pipeline skips the stages whose interface is absent rather than failing the delivery.
322
323
 
323
324
  Provider registrations are managed via the `sources` database table, not via `SharedConfig`. When the orchestrator connects to the Platform relay, it reads source records from the DB and sends `source.register` messages. Changes to sources (add/remove) are detected via PostgreSQL LISTEN/NOTIFY on the `sources_change` channel and pushed to the Platform via `source.secrets` and `source.register`/`source.deregister`.
324
325
 
@@ -406,10 +407,11 @@ GitHub --> Platform Relay --> Orchestrator --> Agent
406
407
  12. **Orchestrator extracts registrations** on default-branch pushes: persists registerable workflows (event, schedule, lifecycle triggers) for cluster-wide event matching.
407
408
  13. **Orchestrator notifies the event router** on default-branch pushes: after the registrations are persisted, emits a `registration.updated` event via `eventRouter.emit()` (if event routing is active). Workflow event subscriptions are the persisted registrations themselves, matched at emit time through the registration index.
408
409
  14. **Orchestrator fetches changed files** via the provider's `ChangedFilesFetcher` for path-based trigger filtering (skipped when no workflow uses path filters).
409
- 15. **Orchestrator matches triggers** against lock file using `matchAllWorkflows()` from `@kici-dev/engine`.
410
- 16. **Orchestrator checks caches** for source tarballs and dependency tarballs.
411
- 17. **Orchestrator dispatches jobs** to agents via the job queue and WebSocket.
412
- 18. **Orchestrator persists a delivery row** keyed by `(org_id, delivery_id)` to its own `event_log`, including a pointer to the gzipped payload in object storage. The orchestrator's delivery log is surfaced in the dashboard's Settings → Event log tab. See [`webhook-delivery.md`](https://docs.kici.dev/architecture/webhooks/webhook-delivery/#delivery-log).
410
+ 15. **Orchestrator matches triggers** against the lock file using `matchWorkflowsForEvent()` from `@kici-dev/engine` -- an event-type-bucketed candidate scan that evaluates only the workflows subscribed to this event type. (The single-registration global / cross-source paths evaluate one lock entry at a time via `matchAllWorkflows()`.)
411
+ 16. **Orchestrator applies the content-requirements filter** to the matched candidates: for each trigger that declares `requires`, it reads the named source files at the event's ref through the provider's `FileContentsFetcher` (once per distinct `(repo, sha, path)` via an LRU cache) and evaluates the declarative requirement. Candidates that fail -- or that cannot be evaluated at all (unreadable or oversize content, a fetch error, no fetcher wired) -- are dropped before dispatch with the concrete reason logged. No workflow code runs at this stage. Skipped entirely when no matched trigger declares `requires`.
412
+ 17. **Orchestrator checks caches** for source tarballs and dependency tarballs.
413
+ 18. **Orchestrator dispatches jobs** to agents via the job queue and WebSocket.
414
+ 19. **Orchestrator persists a delivery row** keyed by `(org_id, delivery_id)` to its own `event_log`, including a pointer to the gzipped payload in object storage. The orchestrator's delivery log is surfaced in the dashboard's Settings → Event log tab. See [`webhook-delivery.md`](https://docs.kici.dev/architecture/webhooks/webhook-delivery/#delivery-log).
413
415
 
414
416
  ## Job execution flow
415
417
 
@@ -525,7 +527,7 @@ Execution Job Dispatch --> Execution Agent
525
527
  | |
526
528
  | |-- Download source tarball (sourceTarUrl) -> extract to workDir/.kici/
527
529
  | |-- Download deps tarball (depsUrl) -> verify SHA-256 -> extract to .kici/node_modules/
528
- | |-- Register @kici-dev/shared/ts-loader-hook
530
+ | |-- Register @kici-dev/core/ts-loader-hook
529
531
  | |-- Verify workflow contentHash against lock file (drift guard)
530
532
  | |-- Dynamic-import workflow .ts
531
533
  | |-- Execute steps
@@ -584,7 +586,7 @@ Dep cache misses alone do **not** trigger a build job. Deps are platform-specifi
584
586
 
585
587
  ### Cross-source / no-contentHash workflows
586
588
 
587
- - **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 32.
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 34.
588
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.
589
591
 
590
592
  ### Build deduplication
@@ -829,7 +831,7 @@ Agent Orchestrator
829
831
 
830
832
  ### Registration extraction flow
831
833
 
832
- When code is pushed to the default branch, the orchestrator extracts event-triggered workflows from the lock file and stores them as registrations for cluster-wide event matching.
834
+ When code is pushed to the default branch, the orchestrator extracts registerable workflows from the lock file and stores them as registrations for cluster-wide event matching. Non-Git triggers (`kici_event`, `schedule`, `generic_webhook`, …) live there because they have no per-repo lock-file pipeline to fall back on; Git-provider triggers (`push`, `pr`, `tag`, …) are indexed too, so the cross-source dispatch path can resolve them by `(customer_id, repo_identifier)` when a generic webhook targets an externally-hosted repo. For same-source Git events the per-event lock-file pipeline remains the primary matcher — registration is an additive index.
833
835
 
834
836
  ```
835
837
  Git Push to Default Branch
@@ -842,8 +844,15 @@ GitHub Webhook -> Platform Relay -> Orchestrator Processor
842
844
  |-- extractRegisterableWorkflows(fullLockFile)
843
845
  | |-- For each workflow entry in lock file:
844
846
  | | Check if any trigger type is registerable
845
- | | (kici_event, workflow_complete, job_complete,
846
- | | generic_webhook, schedule, lifecycle)
847
+ | | (the RegisterableTriggerType enum — the non-Git
848
+ | | set kici_event, workflow_complete,
849
+ | | workflows_failed_batch, job_complete,
850
+ | | generic_webhook, schedule, lifecycle, webhook,
851
+ | | plus every Git-provider trigger: push, pr, tag,
852
+ | | comment, review, review_comment, release,
853
+ | | dispatch, create, delete, status, workflow_run,
854
+ | | fork, star, watch)
855
+ | | ... or the workflow has repo patterns (global workflow)
847
856
  | |-- Return array of registerable workflows
848
857
  |
849
858
  |-- globalWorkflowPolicy.isWorkflowRepoAllowed() (if policy configured)
@@ -1221,7 +1230,7 @@ The Platform tier exposes a `/ws/browser` WebSocket endpoint for dashboard clien
1221
1230
  - [Architecture overview](https://docs.kici.dev/architecture/overview/) -- three-tier model and component responsibilities
1222
1231
  - [Protocol messages](https://docs.kici.dev/architecture/protocol-messages/) -- WebSocket message schemas
1223
1232
  - [Event system internals](https://docs.kici.dev/architecture/webhooks/event-system/) -- event router, registration model, cron scheduler
1224
- - [Execution lifecycle](https://docs.kici.dev/architecture/execution/state-machine/) -- run, job, and step status vocabularies and terminal states
1233
+ - [Execution status vocabulary](https://docs.kici.dev/architecture/execution/state-machine/) -- run, job, and step status vocabularies and terminal states
1225
1234
  - [Webhook delivery](https://docs.kici.dev/architecture/webhooks/webhook-delivery/) -- detailed webhook processing pipeline
1226
1235
  - [Operator: dependency caching](https://docs.kici.dev/operator/dependency-caching/) -- configuration guide
1227
1236
  - [Operator: monitoring & tracing](https://docs.kici.dev/operator/observability/monitoring/) -- trace fields and Loki queries
@@ -1320,7 +1329,7 @@ The agent is the execution worker. It runs on customer infrastructure and has fu
1320
1329
  Shared business logic used by all three tiers. Single source of truth for cross-tier concerns. Has no internal `@kici-dev/*` dependencies -- only a handful of third-party libraries.
1321
1330
 
1322
1331
  - Protocol message schemas (Zod-based, direction-specific unions including dashboard REST-over-WS, browser live streaming, the test-relay control plane, log pull, run events, peer-to-peer, cluster join, and source registration)
1323
- - Provider interfaces (WebhookNormalizer, LockFileFetcher, ChangedFilesFetcher, CloneTokenProvider, RepoUrlBuilder, ContributorResolver, CheckStatusPoster)
1332
+ - Provider interfaces (WebhookNormalizer, LockFileFetcher, ChangedFilesFetcher, FileContentsFetcher, CloneTokenProvider, RepoUrlBuilder, ContributorResolver, CheckStatusPoster)
1324
1333
  - Trigger matching engine (branch, path, event evaluation)
1325
1334
  - Dispatch inputs (input descriptors, extraction from the trigger event, and coercion to typed values)
1326
1335
  - Matrix expansion and fanout (combination expansion with include/exclude, job-name suffix formatting, and materialization of one matrix or multi-host job into N dispatchable children)
@@ -1336,11 +1345,16 @@ Shared business logic used by all three tiers. Single source of truth for cross-
1336
1345
  - Build provenance (in-toto statement schema, DSSE envelope, attestation bundle, verification)
1337
1346
  - Artifact name contract (the shared filesystem/URL-safe name schema the orchestrator, agent, and SDK all validate against)
1338
1347
  - Developer MCP tool schemas (argument schemas for the AI-agent tool surface)
1348
+ - Developer-operations contract (one row per workflow-developer operation declaring which entrypoints expose it -- the shared REST API behind the web UI and the `kici` CLI, the AI-agent tool surface, and a curated UI flag -- asserted against each real surface by congruence tests)
1339
1349
  - Label utilities (platform label derivation, runsOn normalization, `kici:*` set-only reserved namespace, role labels)
1340
1350
  - 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`)
1341
1351
  - Audit policy and retention (per-action access-log sampling, warm-retention windows for cold-store eligibility, federated activity row schema)
1342
1352
  - Scaler backend type enum (`container`, `bare-metal`, `firecracker`, `kubernetes`)
1343
1353
  - Registration trigger type enum (registerable trigger discriminator)
1354
+ - 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)
1355
+ - Plan tier vocabulary (the hosted plan tiers and the purchasable subset, shared by the Platform and the browser dashboard)
1356
+ - Infrastructure alert vocabulary (the diagnostics alert types and severities the Platform mints and the dashboard and `kici` CLI render)
1357
+ - Metric catalog (the generated Prometheus metric inventory, its naming policy, and metric-kind compatibility checks)
1344
1358
  - Bundler config (shared bundler configuration consumed by `e2e/helpers/service-deploy.ts`; the agent runtime uses the `@kici-dev/core/ts-loader-hook` to transform TypeScript on import, with no runtime bundler step)
1345
1359
 
1346
1360
  > Source: `packages/engine/src/`
@@ -1464,7 +1478,7 @@ KiCI uses application-level tenant isolation. The Platform dashboard API accepts
1464
1478
  ## See also
1465
1479
 
1466
1480
  - [Multi-Orchestrator Architecture](https://docs.kici.dev/architecture/clustering/multi-orchestrator/) -- P2P clustering, Raft consensus, job rerouting
1467
- - [Execution lifecycle](https://docs.kici.dev/architecture/execution/state-machine/) -- run, job, and step status vocabularies and the tracker that owns lifecycle state
1481
+ - [Execution status vocabulary](https://docs.kici.dev/architecture/execution/state-machine/) -- run, job, and step status vocabularies and the tracker that owns lifecycle state
1468
1482
  - [Protocol Messages](https://docs.kici.dev/architecture/protocol-messages/) -- WebSocket message schemas for all three layers
1469
1483
  - [Webhook Delivery](https://docs.kici.dev/architecture/webhooks/webhook-delivery/) -- end-to-end trace of a webhook through all three tiers
1470
1484
 
@@ -702,12 +702,13 @@ companion at [Operator troubleshooting](https://docs.kici.dev/operator/troublesh
702
702
 
703
703
  ## Fast triage
704
704
 
705
- | You see... | Jump to |
706
- | -------------------------------------------------------------- | ------------------------------------------------------- |
707
- | A run finishes with `No jobs dispatched` | [No jobs dispatched](https://docs.kici.dev/user/common-failures/#no-jobs-dispatched) |
708
- | A run fails complaining the lock file is stale or incompatible | [Lock-file drift](https://docs.kici.dev/user/common-failures/#lock-file-drift) |
709
- | You pushed but no run ever appears | [The webhook never arrives](https://docs.kici.dev/user/common-failures/#the-webhook-never-arrives) |
710
- | A run is stuck "queued" and no agent ever picks it up | [The agent won't connect](https://docs.kici.dev/user/common-failures/#the-agent-wont-connect) |
705
+ | You see... | Jump to |
706
+ | -------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
707
+ | A run finishes with `No jobs dispatched` | [No jobs dispatched](https://docs.kici.dev/user/common-failures/#no-jobs-dispatched) |
708
+ | A run fails complaining the lock file is stale or incompatible | [Lock-file drift](https://docs.kici.dev/user/common-failures/#lock-file-drift) |
709
+ | You pushed but no run ever appears | [The webhook never arrives](https://docs.kici.dev/user/common-failures/#the-webhook-never-arrives) |
710
+ | A run is stuck "queued" and no agent ever picks it up | [The agent won't connect](https://docs.kici.dev/user/common-failures/#the-agent-wont-connect) |
711
+ | A `commitMessage`-gated workflow stops running for some events | [A `commitMessage` filter never evaluates](https://docs.kici.dev/user/common-failures/#a-commitmessage-filter-never-evaluates) |
711
712
 
712
713
  ## No jobs dispatched
713
714
 
@@ -847,6 +848,24 @@ the unpullable image) — hand this to your operator with the run's failure reas
847
848
  The full provisioning-failure playbook is in
848
849
  [Operator troubleshooting](https://docs.kici.dev/operator/troubleshooting/).
849
850
 
851
+ ## A `commitMessage` filter never evaluates
852
+
853
+ **Symptom.** A workflow gated on a `commitMessage` trigger filter stops running
854
+ for some events, even though the message looks like it should match.
855
+
856
+ **Cause.** The event carries no commit message. A branch-deletion push has no
857
+ head commit, and a self-hosted forge (Gogs, or a GitLab source) may publish none
858
+ at the configured path. The filter is **fail-visible**: when it cannot read a
859
+ message, the workflow does not run rather than running ungated.
860
+
861
+ **Diagnose.** The decision trace records the `commitMessage` check with the
862
+ verdict `indeterminate` and the reason `no commit message in payload`. That is
863
+ distinct from an `excluded` verdict, which the message itself caused.
864
+
865
+ **Fix.** For a self-hosted forge, set the source's `commitMessage` payload path so
866
+ the orchestrator can read the head commit's message. A branch-deletion push
867
+ genuinely carries no message and is expected not to match.
868
+
850
869
  ## When to escalate to your operator
851
870
 
852
871
  The failures above are ones you can resolve from your workflow repo and the
@@ -1140,6 +1159,7 @@ Each workflow entry includes:
1140
1159
  | `concurrency` | Workflow-level concurrency config: `hasGroup`, `cancelInProgress`, `max` (optional). See [concurrency groups](https://docs.kici.dev/user/concurrency/). |
1141
1160
  | `timeout` | Whole-run wall-clock timeout in milliseconds (optional). The orchestrator reads this at run creation to set the run deadline. |
1142
1161
  | `approval` | Normalized approval gate (optional): `clauses`, `reason`, `timeoutSeconds`, `when`. When present the whole run is held before any job is dispatched. Job and step entries carry the same normalized block for job- and step-level gates. See [approval gates](https://docs.kici.dev/user/approvals/). |
1162
+ | `hasFilter` | `true` when the workflow declares a workflow-level `filter` predicate (optional; omitted rather than `false`). The predicate itself is never serialized — the flag tells the orchestrator an agent must evaluate the workflow before any of its jobs is dispatched. See [global workflows](https://docs.kici.dev/user/global-workflows/#narrowing-with-a-filter). |
1143
1163
  | Hook flags | Boolean flags (`hasOnCancel`, `hasCleanup`, `hasOnSuccess`, `hasOnFailure`) indicating which lifecycle hooks are defined. Job entries additionally have `hasBeforeStep` and `hasAfterStep`. |
1144
1164
 
1145
1165
  Step entries carry their own capability flags, so the orchestrator can reason about a step without loading your TypeScript: