@kici-dev/sdk 0.1.27 → 0.3.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 (52) hide show
  1. package/dist/api-types.d.ts +28 -0
  2. package/dist/api-types.js +3 -1
  3. package/dist/approval.d.ts +3 -8
  4. package/dist/approval.js +9 -0
  5. package/dist/artifacts-types.d.ts +45 -0
  6. package/dist/artifacts-types.js +3 -0
  7. package/dist/context.d.ts +65 -15
  8. package/dist/events/define-event.d.ts +11 -3
  9. package/dist/events/define-event.js +14 -4
  10. package/dist/events/emit-typing.test-d.d.ts +2 -0
  11. package/dist/events/emit-typing.test-d.js +36 -0
  12. package/dist/events/event-payloads.d.ts +4 -0
  13. package/dist/events/index.d.ts +1 -1
  14. package/dist/events/index.js +2 -2
  15. package/dist/fleet/agent-version-converge.d.ts +23 -0
  16. package/dist/fleet/agent-version-converge.js +58 -0
  17. package/dist/index.d.ts +9 -3
  18. package/dist/index.js +6 -2
  19. package/dist/job-outputs.test-d.d.ts +2 -0
  20. package/dist/job-outputs.test-d.js +164 -0
  21. package/dist/job.d.ts +71 -9
  22. package/dist/job.js +14 -0
  23. package/dist/matrix/expand.d.ts +1 -1
  24. package/dist/matrix/expand.js +2 -2
  25. package/dist/needs-context.d.ts +13 -6
  26. package/dist/outputs.d.ts +19 -4
  27. package/dist/outputs.js +21 -7
  28. package/dist/parallel.d.ts +1 -1
  29. package/dist/parallel.js +1 -1
  30. package/dist/provenance-types.d.ts +3 -3
  31. package/dist/rules/context.d.ts +33 -0
  32. package/dist/rules/context.js +51 -0
  33. package/dist/rules/evaluator.d.ts +10 -0
  34. package/dist/rules/evaluator.js +9 -1
  35. package/dist/rules/index.d.ts +2 -0
  36. package/dist/rules/index.js +2 -1
  37. package/dist/rules/types.d.ts +11 -1
  38. package/dist/step.d.ts +6 -6
  39. package/dist/step.js +6 -3
  40. package/dist/testing/index.d.ts +2 -0
  41. package/dist/testing/index.js +3 -0
  42. package/dist/testing/step-context.d.ts +56 -0
  43. package/dist/testing/step-context.js +259 -0
  44. package/dist/triggers/index.d.ts +2 -1
  45. package/dist/triggers/index.js +2 -1
  46. package/dist/triggers/types.d.ts +23 -1
  47. package/dist/triggers/workflows-failed-batch.d.ts +20 -0
  48. package/dist/triggers/workflows-failed-batch.js +25 -0
  49. package/dist/types.d.ts +142 -9
  50. package/dist/validation/dag.js +11 -3
  51. package/package.json +10 -5
  52. package/sbom.spdx.json +192 -192
package/dist/types.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { $ as Shell } from 'zx';
3
3
  import type { RetryBackoff } from '@kici-dev/core';
4
4
  import type { ResourceRequest, RunsOnAllInput, OnUnreachableMode, NeedsWhen, ExecutionJobStatus } from '@kici-dev/engine';
5
5
  import type { StepContext, Logger } from './context.js';
6
+ import type { SingleNeedEntry, NeedsContext } from './needs-context.js';
6
7
  import type { TriggerConfig } from './triggers/types.js';
7
8
  import type { Rule } from './rules/types.js';
8
9
  import type { Matrix, MatrixInclude, MatrixExclude } from './matrix/types.js';
@@ -33,6 +34,78 @@ export interface SourceLocation {
33
34
  export type OutputProxy<T> = {
34
35
  readonly [K in keyof T]: T[K];
35
36
  };
37
+ /** Collapse a union of object types into their intersection (`{a} | {b}` -> `{a} & {b}`). */
38
+ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
39
+ /**
40
+ * A single step's contribution to its job's merged output map. A named,
41
+ * non-void step contributes `{ [name]: result }`; an id-less step (empty name)
42
+ * or a void step contributes nothing; a bare function / parallel group in the
43
+ * steps array contributes nothing. Used by {@link InferJobOutputsFromSteps}.
44
+ */
45
+ type StepOutputEntry<TStep> = TStep extends Step<infer TResult, infer TName> ? TName extends '' ? object : [TResult] extends [void] ? object : {
46
+ readonly [K in TName]: TResult;
47
+ } : object;
48
+ /**
49
+ * Merge every named, non-void step's outputs into the job's nested output map,
50
+ * keyed by step name — reproducing the runtime shape `jobRef.result.<step>.<field>`
51
+ * for a multi-step job. When no step contributes a name (all id-less or void),
52
+ * falls back to the loose `Record<string, unknown>` so untyped code keeps
53
+ * compiling. The `run:` shorthand does NOT go through this helper: it infers a
54
+ * flat shape from the run function's return type at the `job()` overload.
55
+ */
56
+ export type InferJobOutputsFromSteps<TSteps extends readonly unknown[]> = UnionToIntersection<{
57
+ [I in keyof TSteps]: StepOutputEntry<TSteps[I]>;
58
+ }[number]> extends infer M ? keyof M extends never ? Record<string, unknown> : {
59
+ readonly [K in keyof M]: M[K];
60
+ } : never;
61
+ /**
62
+ * One `needs` tuple element's contribution to the typed `ctx.needs` map. A job
63
+ * reference with a literal name, or a `'name'` string / `{ name }` object,
64
+ * contributes `{ [name]: SingleNeedEntry<T> }` (outputs typed for a job ref,
65
+ * loose for a string/name); a name-less job, a group ref, or a `{ group }`
66
+ * entry contributes nothing typed (they fall back to the loose map).
67
+ */
68
+ type NeedFromTupleEntry<E> = E extends Job<infer TOut, infer TName> ? string extends TName ? object : TName extends '' ? object : {
69
+ readonly [K in TName]: SingleNeedEntry<TOut>;
70
+ } : E extends string ? string extends E ? object : {
71
+ readonly [K in E]: SingleNeedEntry<Record<string, unknown>>;
72
+ } : E extends {
73
+ name: infer N;
74
+ } ? N extends string ? string extends N ? object : {
75
+ readonly [K in N]: SingleNeedEntry<Record<string, unknown>>;
76
+ } : object : object;
77
+ /**
78
+ * True when at least one `needs` tuple element cannot be keyed (a group ref, a
79
+ * `{ group }` entry, or a name-less job) — i.e. its {@link NeedFromTupleEntry}
80
+ * contributes no key. Such an element would be silently dropped from a strict
81
+ * closed map, so its presence forces the loose fallback.
82
+ */
83
+ type HasUnkeyableNeed<TNeeds extends readonly unknown[]> = true extends {
84
+ [I in keyof TNeeds]: keyof NeedFromTupleEntry<TNeeds[I]> extends never ? true : false;
85
+ }[number] ? true : false;
86
+ /**
87
+ * Derive the typed `ctx.needs` map from a job's `needs` tuple. The strict, closed
88
+ * map (keyed by upstream name — job references carry their inferred outputs,
89
+ * string / `{ name }` entries key loose) is produced ONLY when every entry is
90
+ * keyable, so reading an undeclared need is a compile error. If any entry cannot
91
+ * be keyed (a group reference, a `{ group }` entry, or a name-less job), the whole
92
+ * map falls back to the loose {@link NeedsContext} — the group's key stays
93
+ * accessible and no valid read regresses to a type error.
94
+ */
95
+ export type NeedsFromTuple<TNeeds extends readonly unknown[]> = HasUnkeyableNeed<TNeeds> extends true ? NeedsContext : UnionToIntersection<{
96
+ [I in keyof TNeeds]: NeedFromTupleEntry<TNeeds[I]>;
97
+ }[number]> extends infer M ? keyof M extends never ? NeedsContext : {
98
+ readonly [K in keyof M]: M[K];
99
+ } : never;
100
+ /**
101
+ * The step/run context with its `needs` narrowed to the typed map derived from
102
+ * the enclosing job's `needs` tuple. Used as the contextual type of the `run:`
103
+ * shorthand so `ctx.needs.<job>.result.<field>` is checked. `needs` is required
104
+ * here (a job with declared needs always has them at runtime).
105
+ */
106
+ export type StepContextWithNeeds<TNeeds extends readonly unknown[]> = Omit<StepContext, 'needs'> & {
107
+ needs: NeedsFromTuple<TNeeds>;
108
+ };
36
109
  /** Output schema type - record of Zod types */
37
110
  export type OutputSchema = Record<string, z.ZodTypeAny>;
38
111
  /** Infer the output type from an output schema */
@@ -43,10 +116,15 @@ export type InferOutputs<T extends OutputSchema> = z.infer<z.ZodObject<T>>;
43
116
  * TResult is the inferred return type of the run function (defaults to void for backward compat).
44
117
  * The optional `outputs` field holds a Zod schema for runtime validation but does NOT drive the generic.
45
118
  */
46
- export interface Step<TResult = void> {
119
+ export interface Step<TResult = void, TName extends string = string> {
47
120
  readonly _tag: 'Step';
48
- /** Step name. Empty string for id-less steps (compiler assigns counter IDs). */
49
- readonly name: string;
121
+ /**
122
+ * Step name, carried as a literal type. Empty string (`''`) for id-less steps
123
+ * (the compiler assigns counter IDs at lock generation). The literal name is
124
+ * what lets a named step contribute a typed key to its job's merged output map
125
+ * (see {@link InferJobOutputsFromSteps}).
126
+ */
127
+ readonly name: TName;
50
128
  /** Optional Zod schema for runtime output validation. */
51
129
  readonly outputs?: OutputSchema;
52
130
  readonly run: (ctx: StepContext, drift?: any) => Promise<TResult>;
@@ -324,6 +402,29 @@ export interface ContainerConfig {
324
402
  /** Additional environment variables for the container */
325
403
  env?: Record<string, string>;
326
404
  }
405
+ /**
406
+ * Per-job sandbox escape hatch for container-sandbox jobs (a job with a
407
+ * `container:` image). Requests are granted only within the operator's
408
+ * allow-list (`sandboxAllowedCapabilities` / `sandboxAllowHostNetwork`); a
409
+ * request for anything not allow-listed FAILS the run at dispatch with a reason
410
+ * naming the offending capability or knob — never a silent downgrade.
411
+ */
412
+ export interface SandboxOptions {
413
+ /**
414
+ * Extra Linux capabilities to add back over the hardened `CapDrop:['ALL']`
415
+ * baseline (e.g. `['NET_ADMIN']`). Each requested capability must be
416
+ * allow-listed by the operator via `sandboxAllowedCapabilities`. Unknown
417
+ * capability names are rejected at author time.
418
+ */
419
+ capabilities?: string[];
420
+ /**
421
+ * Container network posture. `'host'` shares the host network namespace and
422
+ * is the escalation — it requires the operator to enable
423
+ * `sandboxAllowHostNetwork`. `'none'` (loopback-only) and `'default'` (bridge)
424
+ * are always allowed (they do not escalate).
425
+ */
426
+ network?: 'default' | 'none' | 'host';
427
+ }
327
428
  /**
328
429
  * Generic per-job initialization config. Runs a hand-written command after the
329
430
  * repo is cloned and before the job's steps execute, so a repo-declared
@@ -424,10 +525,25 @@ export type RunsOnPick = 'deterministic' | 'any';
424
525
  * for portable pool targeting. Users still cannot *set* `kici:` labels on agents.
425
526
  */
426
527
  export type RunsOn = string | RegExp | (string | RegExp)[] | RunsOnSelector;
427
- /** Job definition returned by job() factory */
428
- export interface Job {
528
+ /**
529
+ * Job definition returned by job() factory.
530
+ *
531
+ * `TOutputs` is the job's inferred output shape, threaded onto `result` so
532
+ * cross-job reads (`jobRef.result.<step>.<field>`) are type-checked. It is
533
+ * inferred by {@link InferJobOutputsFromSteps} for a `steps:` job (nested by
534
+ * step name) or from the run function's return type for a `run:` shorthand job
535
+ * (flat), with an explicit override available (`job<T>(...)`). Defaults to the
536
+ * loose `Record<string, unknown>` so a bare `Job` and untyped code keep
537
+ * compiling.
538
+ *
539
+ * `TName` is the job's name carried as a literal (from the `job('name', …)`
540
+ * argument), which lets a typed `needs: [jobRef]` key the reference under its
541
+ * name in `ctx.needs`. Name-less jobs (auto-generated id) keep the loose
542
+ * `string` default.
543
+ */
544
+ export interface Job<TOutputs = Record<string, unknown>, TName extends string = string> {
429
545
  readonly _tag: 'Job';
430
- readonly name: string;
546
+ readonly name: TName;
431
547
  /** Single-agent targeting. Mutually exclusive with `runsOnAll`. */
432
548
  readonly runsOn?: RunsOn;
433
549
  /**
@@ -471,6 +587,8 @@ export interface Job {
471
587
  readonly checkout?: boolean;
472
588
  /** Docker image for job execution. All steps run inside the container. */
473
589
  readonly container?: string | ContainerConfig;
590
+ /** Per-job sandbox escape hatch (container jobs only); granted within the operator allow-list. */
591
+ readonly sandbox?: SandboxOptions;
474
592
  /** Bound context for this job. String for static, or a function of the normalized event envelope for dynamic (resolved at orchestrator two-phase eval). */
475
593
  readonly context?: string | ((event: EventPayload) => string | Promise<string>);
476
594
  /**
@@ -530,9 +648,18 @@ export interface Job {
530
648
  * Type-safe proxy for accessing this job's outputs.
531
649
  * For multi-step jobs: jobRef.result.stepName.field
532
650
  * For single-step (run shorthand) jobs: jobRef.result.field
533
- * At runtime, resolves against the shared job outputs map populated by the workflow runner.
534
- */
535
- readonly result: OutputProxy<any>;
651
+ * At runtime, resolves against the shared job outputs map populated by the
652
+ * workflow runner.
653
+ *
654
+ * The `[TOutputs] extends [void]` guard mirrors `Step.result` so a void
655
+ * run-shorthand job resolves `result` to `never` — which keeps `Job<void>`
656
+ * assignable to `JobOrFactory` (`never` is assignable to
657
+ * `OutputProxy<Record<string, unknown>>`). The tuple wrap is deliberate and
658
+ * non-distributive: it must not distribute over a union output map. Do not
659
+ * "align" this with `Step.result`'s naked form — that would reintroduce
660
+ * distribution over unions.
661
+ */
662
+ readonly result: [TOutputs] extends [void] ? never : OutputProxy<TOutputs>;
536
663
  }
537
664
  /** Options for job() factory */
538
665
  export interface JobOptions {
@@ -619,6 +746,12 @@ export interface JobOptions {
619
746
  * When set, all steps run inside the container.
620
747
  */
621
748
  container?: string | ContainerConfig;
749
+ /**
750
+ * Per-job sandbox escape hatch (container jobs only). Request extra Linux
751
+ * capabilities / host networking; granted only within the operator's
752
+ * `org_settings` allow-list, else the run fails loudly at dispatch.
753
+ */
754
+ sandbox?: SandboxOptions;
622
755
  /** Bound context for this job. String for static, or a function of the normalized event envelope for dynamic (resolved at orchestrator two-phase eval). */
623
756
  context?: string | ((event: EventPayload) => string | Promise<string>);
624
757
  /**
@@ -51,12 +51,20 @@ function validateDag(nodes) {
51
51
  }
52
52
  }
53
53
  if (sortedOrder.length !== nodes.length) {
54
- const cycleNodes = [];
55
- for (const [id, degree] of inDegree) if (degree > 0) cycleNodes.push(id);
54
+ const residual = /* @__PURE__ */ new Set();
55
+ for (const [id, degree] of inDegree) if (degree > 0) residual.add(id);
56
+ let changed = true;
57
+ while (changed) {
58
+ changed = false;
59
+ for (const id of residual) if (!(adjacencyList.get(id) ?? []).some((target) => residual.has(target))) {
60
+ residual.delete(id);
61
+ changed = true;
62
+ }
63
+ }
56
64
  return {
57
65
  valid: false,
58
66
  error: "cycle",
59
- nodesInCycle: cycleNodes
67
+ nodesInCycle: [...residual]
60
68
  };
61
69
  }
62
70
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kici-dev/sdk",
3
- "version": "0.1.27",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript SDK for defining KiCI workflows. Import into `.kici/workflows/*.ts` to declare workflows, jobs, steps, triggers, rules, and matrix configurations.",
5
5
  "keywords": [
6
6
  "ci",
@@ -43,21 +43,26 @@
43
43
  "types": "./dist/index.d.ts",
44
44
  "import": "./dist/index.js",
45
45
  "default": "./dist/index.js"
46
+ },
47
+ "./testing": {
48
+ "types": "./dist/testing/index.d.ts",
49
+ "import": "./dist/testing/index.js",
50
+ "default": "./dist/testing/index.js"
46
51
  }
47
52
  },
48
53
  "dependencies": {
49
54
  "micromatch": "^4.0.8",
50
55
  "zod": "^4.4.3",
51
56
  "zx": "^8.8.5",
52
- "@kici-dev/engine": "0.1.27",
53
- "@kici-dev/core": "0.1.27"
57
+ "@kici-dev/core": "0.3.0",
58
+ "@kici-dev/engine": "0.3.0"
54
59
  },
55
60
  "devDependencies": {
56
61
  "@types/micromatch": "^4.0.10"
57
62
  },
58
63
  "scripts": {
59
- "build": "node ../../scripts/build-ts.mjs && tsc --emitDeclarationOnly",
60
- "typecheck": "tsc --noEmit",
64
+ "build": "node ../../scripts/build-ts.mjs && tsgo --emitDeclarationOnly",
65
+ "typecheck": "tsgo --noEmit",
61
66
  "test": "vitest run",
62
67
  "test:watch": "vitest"
63
68
  }