@kici-dev/engine 0.3.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 (45) hide show
  1. package/dist/context/host-match.js +2 -1
  2. package/dist/context/index.d.ts +1 -0
  3. package/dist/context/index.js +2 -1
  4. package/dist/context/secret-key.d.ts +48 -0
  5. package/dist/context/secret-key.js +64 -0
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.js +8 -5
  8. package/dist/labels/compile.d.ts +2 -7
  9. package/dist/labels/compile.js +1 -10
  10. package/dist/labels.d.ts +8 -0
  11. package/dist/labels.js +9 -1
  12. package/dist/metrics/catalog-policy.d.ts +11 -0
  13. package/dist/metrics/catalog-policy.js +13 -3
  14. package/dist/metrics/metric-catalog.generated.d.ts +30 -0
  15. package/dist/metrics/metric-catalog.generated.js +36 -0
  16. package/dist/protocol/messages/dashboard-global-workflows.d.ts +8 -3
  17. package/dist/protocol/messages/dashboard-global-workflows.js +20 -3
  18. package/dist/protocol/messages/dashboard.d.ts +5 -2
  19. package/dist/protocol/messages/dashboard.js +7 -0
  20. package/dist/protocol/messages/execution-status.d.ts +3 -0
  21. package/dist/protocol/messages/execution-status.js +16 -0
  22. package/dist/protocol/messages/orchestrator-agent.d.ts +49 -0
  23. package/dist/protocol/messages/orchestrator-agent.js +34 -1
  24. package/dist/protocol/messages/platform-orchestrator.d.ts +5 -2
  25. package/dist/protocol/messages/platform-orchestrator.js +14 -0
  26. package/dist/provider/check-status-poster.d.ts +14 -0
  27. package/dist/provider/file-contents-fetcher.d.ts +39 -0
  28. package/dist/provider/file-contents-fetcher.js +2 -0
  29. package/dist/provider/index.d.ts +2 -0
  30. package/dist/safe-regex.d.ts +16 -0
  31. package/dist/safe-regex.js +24 -0
  32. package/dist/trigger/compiled-matchers.d.ts +21 -1
  33. package/dist/trigger/compiled-matchers.js +30 -3
  34. package/dist/trigger/content-requirements.d.ts +31 -0
  35. package/dist/trigger/content-requirements.js +125 -0
  36. package/dist/trigger/decision-trace.d.ts +89 -0
  37. package/dist/trigger/decision-trace.js +96 -1
  38. package/dist/trigger/jsonpath-matcher.js +5 -1
  39. package/dist/trigger/matcher.js +57 -8
  40. package/dist/trigger/text-match.d.ts +27 -0
  41. package/dist/trigger/text-match.js +86 -0
  42. package/dist/trigger/types.d.ts +179 -17
  43. package/dist/trigger/types.js +32 -5
  44. package/package.json +10 -1
  45. package/sbom.spdx.json +30 -5
@@ -0,0 +1,86 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import safeRegex from "safe-regex";
3
+ //#region src/trigger/text-match.ts
4
+ /**
5
+ * The single definition of what `contains` / `notContains` / `matches` /
6
+ * `notMatches` mean.
7
+ *
8
+ * Two sites consume it: the Tier-0 `commitMessage` trigger filter (in
9
+ * `matcher.ts`) and the Tier-1 `requires` content filter (in
10
+ * `content-requirements.ts`). Sharing one function is what keeps them from
11
+ * drifting into two dialects of the same vocabulary.
12
+ *
13
+ * Pure string logic — it imports only `safe-regex` and no Node built-in, so it
14
+ * belongs in the browser-safe barrel rather than a subpath export.
15
+ */
16
+ /**
17
+ * Compile a `/pattern/flags` (or bare-pattern) string, rejecting a ReDoS-prone
18
+ * pattern. Returns `null` when the pattern is syntactically invalid or fails the
19
+ * `safe-regex` star-height heuristic — callers treat `null` as indeterminate.
20
+ *
21
+ * Always compiles a FRESH RegExp. A `g`-flagged instance carries `lastIndex`
22
+ * across `.test()` calls, so a cached one would return alternating verdicts for
23
+ * the same input.
24
+ */
25
+ function compileSafeRegex(source) {
26
+ const wrapped = /^\/(.+)\/([gimsuy]*)$/.exec(source);
27
+ const pattern = wrapped ? wrapped[1] : source;
28
+ const flags = wrapped ? wrapped[2] : "";
29
+ let re;
30
+ try {
31
+ re = new RegExp(pattern, flags);
32
+ } catch {
33
+ return null;
34
+ }
35
+ return safeRegex(re) ? re : null;
36
+ }
37
+ /** True when the matcher carries at least one populated query key. */
38
+ function textMatchHasQuery(m) {
39
+ const populated = (v) => Array.isArray(v) ? v.length > 0 : v !== void 0 && v !== null;
40
+ return populated(m.contains) || populated(m.notContains) || populated(m.matches) || populated(m.notMatches);
41
+ }
42
+ /** Render the populated keys for a decision-trace `pattern` field. */
43
+ function describeTextMatch(m) {
44
+ const parts = [];
45
+ if (m.contains?.length) parts.push(`contains: [${m.contains.join(", ")}]`);
46
+ if (m.notContains?.length) parts.push(`notContains: [${m.notContains.join(", ")}]`);
47
+ if (m.matches?.length) parts.push(`matches: [${m.matches.join(", ")}]`);
48
+ if (m.notMatches?.length) parts.push(`notMatches: [${m.notMatches.join(", ")}]`);
49
+ if (m.ignoreCase) parts.push("ignoreCase");
50
+ return parts.length > 0 ? parts.join("; ") : "(no query)";
51
+ }
52
+ /** Evaluate every regex in `sources`; `expectMatch` selects matches vs notMatches. */
53
+ function evaluateRegexes(text, sources, expectMatch) {
54
+ for (const source of sources) {
55
+ const re = compileSafeRegex(source);
56
+ if (!re) return {
57
+ pass: false,
58
+ indeterminate: `unsafe or invalid regex: ${source}`
59
+ };
60
+ if (re.test(text) !== expectMatch) return { pass: false };
61
+ }
62
+ return { pass: true };
63
+ }
64
+ /**
65
+ * Evaluate a text match. Every entry in every list is a conjunct, and every
66
+ * populated key ANDs with the others; an empty matcher passes.
67
+ */
68
+ function evaluateTextMatch(text, m) {
69
+ const fold = (s) => m.ignoreCase === true ? s.toLowerCase() : s;
70
+ const haystack = fold(text);
71
+ for (const needle of m.contains ?? []) if (!haystack.includes(fold(needle))) return { pass: false };
72
+ for (const needle of m.notContains ?? []) if (haystack.includes(fold(needle))) return { pass: false };
73
+ if (m.matches?.length) {
74
+ const result = evaluateRegexes(text, m.matches, true);
75
+ if (!result.pass) return result;
76
+ }
77
+ if (m.notMatches?.length) {
78
+ const result = evaluateRegexes(text, m.notMatches, false);
79
+ if (!result.pass) return result;
80
+ }
81
+ return { pass: true };
82
+ }
83
+ //#endregion
84
+ export { compileSafeRegex, describeTextMatch, evaluateTextMatch, textMatchHasQuery };
85
+
86
+ //# sourceMappingURL=text-match.js.map
@@ -29,6 +29,10 @@
29
29
  * Schema version 30 (BREAKING): renames job-level `environments` to `contexts`.
30
30
  * Schema version 31 (additive): adds the workflows_failed_batch lock trigger.
31
31
  * Schema version 32 (additive): adds LockJob.sandbox (per-job escape-hatch request).
32
+ * Schema version 33 (additive): adds `requires` (declarative static content filter) to the push/pr/tag git-event triggers.
33
+ * Schema version 34 (additive): adds LockWorkflow.hasFilter (workflow-level pre-dispatch filter predicate).
34
+ * Schema version 35 (additive): adds `commitMessage` (LockTextMatch) to the push/pr/tag
35
+ * git-event triggers, and contains/notContains/notMatches to LockContentRequirement.
32
36
  */
33
37
  import { z } from 'zod';
34
38
  import type { ProviderType } from '../provider/types.js';
@@ -41,7 +45,7 @@ import { ExecutionJobStatus } from '../protocol/messages/execution-status.js';
41
45
  * schema change (additive or breaking); the bump-history comment above records
42
46
  * which. See `BREAKING_FLOOR` for the compatibility-window semantics.
43
47
  */
44
- export declare const SCHEMA_VERSION: 32;
48
+ export declare const SCHEMA_VERSION: 35;
45
49
  /**
46
50
  * Oldest lock schema version this codebase can still read correctly — the lower
47
51
  * bound of the acceptance window.
@@ -54,8 +58,8 @@ export declare const SCHEMA_VERSION: 32;
54
58
  * Bump rule: move this to the current `SCHEMA_VERSION` ONLY in the commit that
55
59
  * lands a `BREAKING` schema change (see the bump-history convention above). It
56
60
  * currently sits at 30 because v30 (`environments`→`contexts`) was the most
57
- * recent breaking bump; v31 and v32 were additive, so a v30 lock still reads
58
- * correctly.
61
+ * recent breaking bump; v31 through v35 were additive, so a v30 lock still
62
+ * reads correctly.
59
63
  */
60
64
  export declare const BREAKING_FLOOR: 30;
61
65
  /**
@@ -92,6 +96,121 @@ export interface LockBranchPattern {
92
96
  readonly pattern: string;
93
97
  readonly flags?: string;
94
98
  }
99
+ /**
100
+ * A declarative query over one piece of text: literal substrings and/or a
101
+ * regex, in either direction. Pure DATA the orchestrator interprets — never
102
+ * author code — so it is safe to evaluate inside the orchestrator under the
103
+ * execution-purity model.
104
+ *
105
+ * Every entry in a list is a CONJUNCT: `contains: [a, b]` means the text
106
+ * contains `a` AND contains `b`. OR is expressed by declaring two triggers,
107
+ * since a workflow's trigger list is already "first match wins".
108
+ *
109
+ * This is the SDK-facing shape a workflow author writes; the compiler
110
+ * normalizes it to {@link LockTextMatch}.
111
+ */
112
+ export interface TextMatch {
113
+ /** Literal substring(s). Every entry must be present. */
114
+ readonly contains?: string | readonly string[];
115
+ /** Literal substring(s). No entry may be present. */
116
+ readonly notContains?: string | readonly string[];
117
+ /** Regex(es), as a RegExp or a `/pattern/flags` string. Every one must match. */
118
+ readonly matches?: string | RegExp | readonly (string | RegExp)[];
119
+ /** Regex(es). None may match. */
120
+ readonly notMatches?: string | RegExp | readonly (string | RegExp)[];
121
+ /**
122
+ * Case-insensitive comparison for `contains`/`notContains` ONLY. Default false.
123
+ * It deliberately does not touch the regex keys: a regex already carries its
124
+ * own flags, and injecting `i` into a pattern whose author omitted it would
125
+ * silently change its meaning.
126
+ */
127
+ readonly ignoreCase?: boolean;
128
+ }
129
+ /**
130
+ * Lock-file form of {@link TextMatch}. The compiler normalizes every key to a
131
+ * flat array, and every regex to a `/pattern/flags` string, so the orchestrator
132
+ * matcher has exactly one shape to interpret.
133
+ */
134
+ export interface LockTextMatch {
135
+ readonly contains?: readonly string[];
136
+ readonly notContains?: readonly string[];
137
+ /** Always in `/pattern/flags` form. */
138
+ readonly matches?: readonly string[];
139
+ /** Always in `/pattern/flags` form. */
140
+ readonly notMatches?: readonly string[];
141
+ readonly ignoreCase?: boolean;
142
+ }
143
+ /**
144
+ * How a file's bytes are parsed before a content query runs.
145
+ * `auto` picks by extension: `.json` → json, `.yaml`/`.yml` → yaml, else text.
146
+ */
147
+ export type ContentFormat = 'json' | 'yaml' | 'text' | 'auto';
148
+ /**
149
+ * Declarative static content filter: a query over the bytes of one source file
150
+ * at the event's ref. Pure DATA the orchestrator's own matcher interprets — never
151
+ * author code — so it is safe to evaluate in the orchestrator (Part B of the
152
+ * execution-purity model). Query keys
153
+ * (`exists`/`match`/`not`/`contains`/`notContains`/`matches`/`notMatches`) are
154
+ * AND-ed within an entry; `absent` is mutually exclusive with them and passes
155
+ * only when the file is missing.
156
+ *
157
+ * This is the SDK-facing shape a workflow author writes, which accepts a scalar
158
+ * or `RegExp` where the lock form ({@link LockContentRequirement}) carries a flat
159
+ * array of `/pattern/flags` strings — the compiler normalizes one to the other.
160
+ */
161
+ export interface ContentRequirement {
162
+ /** Repo-relative path of the file to query. */
163
+ readonly file: string;
164
+ /** Parse format; defaults to `auto` when unset. */
165
+ readonly format?: ContentFormat;
166
+ /** JSONPath expressions that must each resolve to ≥1 node (json/yaml only). */
167
+ readonly exists?: readonly string[];
168
+ /** JSONPath → expected-value map; every expression must match (json/yaml only). */
169
+ readonly match?: Record<string, unknown>;
170
+ /** JSONPath → value map; passes only when NONE match (json/yaml only). */
171
+ readonly not?: Record<string, unknown>;
172
+ /** Literal substring(s) that must ALL be present in the raw file text. */
173
+ readonly contains?: string | readonly string[];
174
+ /** Literal substring(s) of which NONE may be present in the raw file text. */
175
+ readonly notContains?: string | readonly string[];
176
+ /** Regex(es) that must ALL match the raw file text (RegExp or `/pattern/flags`). */
177
+ readonly matches?: string | RegExp | readonly (string | RegExp)[];
178
+ /** Regex(es) of which NONE may match the raw file text. */
179
+ readonly notMatches?: string | RegExp | readonly (string | RegExp)[];
180
+ /** Case-insensitive `contains`/`notContains`. Default false. */
181
+ readonly ignoreCase?: boolean;
182
+ /** When true, the entry passes only if the file is absent. Excludes all query keys. */
183
+ readonly absent?: boolean;
184
+ }
185
+ /**
186
+ * Lock-file form of {@link ContentRequirement}. The compiler normalizes each
187
+ * raw-text key to the flat {@link LockTextMatch} shape, so the orchestrator
188
+ * matcher has one shape to interpret.
189
+ */
190
+ export interface LockContentRequirement {
191
+ readonly file: string;
192
+ readonly format?: ContentFormat;
193
+ readonly exists?: readonly string[];
194
+ readonly match?: Record<string, unknown>;
195
+ readonly not?: Record<string, unknown>;
196
+ readonly contains?: readonly string[];
197
+ readonly notContains?: readonly string[];
198
+ readonly matches?: readonly string[];
199
+ readonly notMatches?: readonly string[];
200
+ readonly ignoreCase?: boolean;
201
+ readonly absent?: boolean;
202
+ }
203
+ /**
204
+ * Resolve a content requirement's parse format to a concrete value. An explicit
205
+ * non-`auto` format is returned as-is; `auto` (or unset) is resolved by the file
206
+ * extension: `.json` → json, `.yaml`/`.yml` → yaml, everything else text.
207
+ *
208
+ * Yaml-free (pure string logic) so it lives in the browser-safe barrel and is
209
+ * the single source of truth for both the compiler's compile-time serializer and
210
+ * the orchestrator's eval-time matcher — the two can never disagree about how a
211
+ * file's format is picked.
212
+ */
213
+ export declare function resolveContentFormat(file: string, format: ContentFormat | undefined): 'json' | 'yaml' | 'text';
95
214
  /**
96
215
  * PR trigger in lock file.
97
216
  * Optimized for orchestrator event matching - flat structure with all filters accessible.
@@ -103,6 +222,10 @@ export interface LockPrTrigger {
103
222
  readonly sourceBranches: readonly LockBranchPattern[];
104
223
  readonly paths: readonly string[];
105
224
  readonly repos?: readonly LockBranchPattern[];
225
+ /** Declarative static content filter over source files at the event ref (AND-ed). */
226
+ readonly requires?: readonly LockContentRequirement[];
227
+ /** Declarative static filter over the event's commit message / PR title+body. */
228
+ readonly commitMessage?: LockTextMatch;
106
229
  }
107
230
  /**
108
231
  * Push trigger in lock file.
@@ -113,6 +236,10 @@ export interface LockPushTrigger {
113
236
  readonly branches: readonly LockBranchPattern[];
114
237
  readonly paths: readonly string[];
115
238
  readonly repos?: readonly LockBranchPattern[];
239
+ /** Declarative static content filter over source files at the event ref (AND-ed). */
240
+ readonly requires?: readonly LockContentRequirement[];
241
+ /** Declarative static filter over the event's commit message / PR title+body. */
242
+ readonly commitMessage?: LockTextMatch;
116
243
  }
117
244
  /**
118
245
  * Tag trigger in lock file.
@@ -122,6 +249,10 @@ export interface LockTagTrigger {
122
249
  readonly _type: 'tag';
123
250
  readonly patterns: readonly LockBranchPattern[];
124
251
  readonly repos?: readonly LockBranchPattern[];
252
+ /** Declarative static content filter over source files at the event ref (AND-ed). */
253
+ readonly requires?: readonly LockContentRequirement[];
254
+ /** Declarative static filter over the event's commit message / PR title+body. */
255
+ readonly commitMessage?: LockTextMatch;
125
256
  }
126
257
  /**
127
258
  * Comment trigger in lock file.
@@ -426,17 +557,27 @@ export type LockStepEntry = LockStep | LockParallelStep;
426
557
  /** Type guard distinguishing a parallel group from an ordinary lock step. */
427
558
  export declare function isLockParallelStep(entry: LockStepEntry): entry is LockParallelStep;
428
559
  /**
429
- * Inline expression value for pure dynamic functions.
430
- * The compiler serializes pure functions as { _type: 'inline', expression: '(event) => ...' }
431
- * and the orchestrator evaluates them via vm.runInNewContext at dispatch time.
432
- * struct with discriminant and expression field.
433
- * _type: 'inline' alongside existing 'static' and 'dynamic' discriminants.
560
+ * Serialized inline expression for a dynamic env/context/concurrencyGroup
561
+ * field, shaped as `{ _type: 'inline', expression: '(event) => ...' }`
562
+ * alongside the existing 'static' and 'dynamic' discriminants.
563
+ *
564
+ * @deprecated Schema v11 inline expressions are no longer evaluated in the
565
+ * orchestrator. Dynamic env/context/concurrencyGroup fields are resolved on the
566
+ * eval agent's init-runner. The compiler no longer emits this type; readers keep
567
+ * recognizing it only to defer an old lock's field to the init round. Removed at
568
+ * the next major (v1.0.0).
434
569
  */
435
570
  export interface LockInlineValue {
436
571
  readonly _type: 'inline';
437
572
  readonly expression: string;
438
573
  }
439
- /** Type guard for inline expression values */
574
+ /**
575
+ * Type guard for inline expression values.
576
+ *
577
+ * @deprecated See {@link LockInlineValue}. Retained only so a reader can
578
+ * recognize an old lock's inline field and defer it to the eval agent's
579
+ * init-runner. Removed at the next major (v1.0.0).
580
+ */
440
581
  export declare function isLockInlineValue(value: unknown): value is LockInlineValue;
441
582
  /**
442
583
  * Author-facing keyword sugar for a `needs` edge's run condition. Each keyword
@@ -640,21 +781,28 @@ export interface LockJob {
640
781
  readonly rules?: readonly LockRule[];
641
782
  readonly description?: string;
642
783
  /**
643
- * Bound contexts in merge order. Each entry is a static name or inline
644
- * expression (pure function); `dynamic` is set when it is a function resolved at
645
- * two-phase eval. Later entries override earlier ones on name collisions.
784
+ * Bound contexts in merge order. Each entry is a static name; `dynamic` is set
785
+ * when it is a function resolved on the eval agent's init-runner. Later entries
786
+ * override earlier ones on name collisions. The `LockInlineValue` shape is a
787
+ * deprecated form still accepted from old locks (see {@link LockInlineValue}).
646
788
  */
647
789
  readonly contexts?: ReadonlyArray<{
648
790
  value: string | LockInlineValue;
649
791
  dynamic: boolean;
650
792
  }>;
651
- /** Static environment variables or inline expression (pure function). */
793
+ /**
794
+ * Static environment variables. A deprecated `LockInlineValue` shape is still
795
+ * accepted from old locks (see {@link LockInlineValue}).
796
+ */
652
797
  readonly env?: Record<string, string> | LockInlineValue;
653
- /** When true, env is dynamic (function) -- resolved at orchestrator two-phase eval or inline. */
798
+ /** When true, env is dynamic (function) -- resolved on the eval agent's init-runner. */
654
799
  readonly dynamicEnv?: boolean;
655
- /** Concurrency group name (static string) or inline expression (pure function). */
800
+ /**
801
+ * Concurrency group name (static string). A deprecated `LockInlineValue` shape
802
+ * is still accepted from old locks (see {@link LockInlineValue}).
803
+ */
656
804
  readonly concurrencyGroup?: string | LockInlineValue;
657
- /** When true, concurrencyGroup is dynamic (function) -- resolved at orchestrator two-phase eval or inline. */
805
+ /** When true, concurrencyGroup is dynamic (function) -- resolved on the eval agent's init-runner. */
658
806
  readonly dynamicConcurrencyGroup?: boolean;
659
807
  /** Total job wall-clock timeout in milliseconds (init + all steps + hooks). Threaded to the agent via jobConfig. */
660
808
  readonly timeout?: number;
@@ -769,6 +917,13 @@ export interface LockWorkflow {
769
917
  readonly timeout?: number;
770
918
  /** Normalized approval gate; when set the whole run is held before any job dispatches. */
771
919
  readonly approval?: LockApproval;
920
+ /**
921
+ * True when the workflow declares a `filter` predicate. A bare flag, not a
922
+ * source reference: `LockWorkflow.source` already identifies the module and
923
+ * export, so the eval agent loads it and reads `.filter` off the workflow
924
+ * object. Mirrors the `dynamicEnv` / `dynamicConcurrencyGroup` convention.
925
+ */
926
+ readonly hasFilter?: boolean;
772
927
  }
773
928
  /**
774
929
  * Complete lock file structure.
@@ -783,7 +938,7 @@ export interface LockWorkflow {
783
938
  * v8 adds runsOn polymorphic type (string | string[] | selector) and excludeLabels.
784
939
  * v9 adds repos/notRepos repo pattern fields to git-event triggers for global workflow matching.
785
940
  * v10 removes notRepos/notPaths fields; negative patterns use ! prefix in repos/paths arrays.
786
- * v11 adds LockInlineValue type for pure function inline evaluation.
941
+ * v11 adds the LockInlineValue type (deprecated; inline dynamic fields are resolved on the eval agent).
787
942
  * v12 adds workflow-level registries and installEnv for private npm registry auth.
788
943
  * v13 adds job-level and workflow-level timeout.
789
944
  */
@@ -869,6 +1024,13 @@ export interface SimulatedEvent {
869
1024
  * to the previous owner.
870
1025
  */
871
1026
  senderUserId?: string;
1027
+ /**
1028
+ * Text a `commitMessage` trigger filter is tested against: the full head-commit
1029
+ * message for push/tag, or PR title + body for pull-request events. Absent when
1030
+ * the provider payload carries none — which a `commitMessage` filter treats as
1031
+ * INDETERMINATE (fail-visible), never as an empty string.
1032
+ */
1033
+ commitMessage?: string;
872
1034
  /** Repository identifier where the event occurred (e.g., "owner/repo").
873
1035
  * Used by global workflow repo pattern matching. */
874
1036
  sourceRepo?: string;
@@ -34,13 +34,17 @@ import { z } from "zod";
34
34
  * Schema version 30 (BREAKING): renames job-level `environments` to `contexts`.
35
35
  * Schema version 31 (additive): adds the workflows_failed_batch lock trigger.
36
36
  * Schema version 32 (additive): adds LockJob.sandbox (per-job escape-hatch request).
37
+ * Schema version 33 (additive): adds `requires` (declarative static content filter) to the push/pr/tag git-event triggers.
38
+ * Schema version 34 (additive): adds LockWorkflow.hasFilter (workflow-level pre-dispatch filter predicate).
39
+ * Schema version 35 (additive): adds `commitMessage` (LockTextMatch) to the push/pr/tag
40
+ * git-event triggers, and contains/notContains/notMatches to LockContentRequirement.
37
41
  */
38
42
  /**
39
43
  * Schema version the compiler emits into every lock file. Incremented on ANY
40
44
  * schema change (additive or breaking); the bump-history comment above records
41
45
  * which. See `BREAKING_FLOOR` for the compatibility-window semantics.
42
46
  */
43
- const SCHEMA_VERSION = 32;
47
+ const SCHEMA_VERSION = 35;
44
48
  /**
45
49
  * Oldest lock schema version this codebase can still read correctly — the lower
46
50
  * bound of the acceptance window.
@@ -53,15 +57,38 @@ const SCHEMA_VERSION = 32;
53
57
  * Bump rule: move this to the current `SCHEMA_VERSION` ONLY in the commit that
54
58
  * lands a `BREAKING` schema change (see the bump-history convention above). It
55
59
  * currently sits at 30 because v30 (`environments`→`contexts`) was the most
56
- * recent breaking bump; v31 and v32 were additive, so a v30 lock still reads
57
- * correctly.
60
+ * recent breaking bump; v31 through v35 were additive, so a v30 lock still
61
+ * reads correctly.
58
62
  */
59
63
  const BREAKING_FLOOR = 30;
64
+ /**
65
+ * Resolve a content requirement's parse format to a concrete value. An explicit
66
+ * non-`auto` format is returned as-is; `auto` (or unset) is resolved by the file
67
+ * extension: `.json` → json, `.yaml`/`.yml` → yaml, everything else text.
68
+ *
69
+ * Yaml-free (pure string logic) so it lives in the browser-safe barrel and is
70
+ * the single source of truth for both the compiler's compile-time serializer and
71
+ * the orchestrator's eval-time matcher — the two can never disagree about how a
72
+ * file's format is picked.
73
+ */
74
+ function resolveContentFormat(file, format) {
75
+ if (format && format !== "auto") return format;
76
+ const lower = file.toLowerCase();
77
+ if (lower.endsWith(".json")) return "json";
78
+ if (lower.endsWith(".yaml") || lower.endsWith(".yml")) return "yaml";
79
+ return "text";
80
+ }
60
81
  /** Type guard distinguishing a parallel group from an ordinary lock step. */
61
82
  function isLockParallelStep(entry) {
62
83
  return entry.kind === "parallel";
63
84
  }
64
- /** Type guard for inline expression values */
85
+ /**
86
+ * Type guard for inline expression values.
87
+ *
88
+ * @deprecated See {@link LockInlineValue}. Retained only so a reader can
89
+ * recognize an old lock's inline field and defer it to the eval agent's
90
+ * init-runner. Removed at the next major (v1.0.0).
91
+ */
65
92
  function isLockInlineValue(value) {
66
93
  return typeof value === "object" && value !== null && value._type === "inline";
67
94
  }
@@ -178,6 +205,6 @@ const changedFilesStatusSchema = z.enum([
178
205
  "skipped"
179
206
  ]);
180
207
  //#endregion
181
- export { BREAKING_FLOOR, NeedsEntrySchema, NeedsGroupEntrySchema, NeedsRunOn, NeedsWhen, OnUnreachableMode, RunsOnPick, SANDBOX_NETWORK_MODES, SCHEMA_VERSION, changedFilesStatusSchema, isLockDynamicJobFn, isLockInlineValue, isLockParallelStep, isLockStaticJob, resolveWhenToRunOn };
208
+ export { BREAKING_FLOOR, NeedsEntrySchema, NeedsGroupEntrySchema, NeedsRunOn, NeedsWhen, OnUnreachableMode, RunsOnPick, SANDBOX_NETWORK_MODES, SCHEMA_VERSION, changedFilesStatusSchema, isLockDynamicJobFn, isLockInlineValue, isLockParallelStep, isLockStaticJob, resolveContentFormat, resolveWhenToRunOn };
182
209
 
183
210
  //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kici-dev/engine",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Shared business logic for the KiCI CI/CD stack: protocol, triggers, state machine, and provider interfaces used by the Platform relay, orchestrator, and compiler.",
5
5
  "keywords": [
6
6
  "ci",
@@ -45,10 +45,18 @@
45
45
  "import": "./dist/trigger/trigger-event-type.js",
46
46
  "types": "./dist/trigger/trigger-event-type.d.ts"
47
47
  },
48
+ "./trigger/content-requirements": {
49
+ "import": "./dist/trigger/content-requirements.js",
50
+ "types": "./dist/trigger/content-requirements.d.ts"
51
+ },
48
52
  "./webhook/signature": {
49
53
  "import": "./dist/webhook/signature.js",
50
54
  "types": "./dist/webhook/signature.d.ts"
51
55
  },
56
+ "./safe-regex": {
57
+ "import": "./dist/safe-regex.js",
58
+ "types": "./dist/safe-regex.d.ts"
59
+ },
52
60
  "./labels/compile": {
53
61
  "import": "./dist/labels/compile.js",
54
62
  "types": "./dist/labels/compile.d.ts"
@@ -135,6 +143,7 @@
135
143
  "jsonpath-plus": "^10.4.0",
136
144
  "picomatch": "^4.0.5",
137
145
  "safe-regex": "^2.1.1",
146
+ "yaml": "^2.9.0",
138
147
  "zod": "^4.4.3"
139
148
  },
140
149
  "devDependencies": {
package/sbom.spdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "@kici-dev/engine@0.3.0",
6
- "documentNamespace": "https://kici.dev/sbom/%40kici-dev%2Fengine/0.3.0/efa4dc6e-9041-4801-bb71-cb5f0e5d0cc2",
5
+ "name": "@kici-dev/engine@0.5.0",
6
+ "documentNamespace": "https://kici.dev/sbom/%40kici-dev%2Fengine/0.5.0/4dbbd65e-049d-4dc9-8c49-6e9d7ed4bc9e",
7
7
  "creationInfo": {
8
- "created": "2026-08-09T01:30:10Z",
8
+ "created": "2026-08-15T19:15:34Z",
9
9
  "creators": [
10
10
  "Tool: kici-sbom-generator"
11
11
  ]
@@ -54,7 +54,7 @@
54
54
  {
55
55
  "SPDXID": "SPDXRef-RootPackage",
56
56
  "name": "@kici-dev/engine",
57
- "versionInfo": "0.3.0",
57
+ "versionInfo": "0.5.0",
58
58
  "downloadLocation": "NOASSERTION",
59
59
  "filesAnalyzed": false,
60
60
  "licenseConcluded": "NOASSERTION",
@@ -65,7 +65,7 @@
65
65
  {
66
66
  "referenceCategory": "PACKAGE-MANAGER",
67
67
  "referenceType": "purl",
68
- "referenceLocator": "pkg:npm/%40kici-dev/engine@0.3.0"
68
+ "referenceLocator": "pkg:npm/%40kici-dev/engine@0.5.0"
69
69
  }
70
70
  ],
71
71
  "description": "Shared business logic for the KiCI CI/CD stack: protocol, triggers, state machine, and provider interfaces used by the Platform relay, orchestrator, and compiler.",
@@ -191,6 +191,26 @@
191
191
  "description": "detect possibly catastrophic, exponential-time regular expressions",
192
192
  "homepage": "https://github.com/davisjam/safe-regex"
193
193
  },
194
+ {
195
+ "SPDXID": "SPDXRef-Package-yaml-2.9.0",
196
+ "name": "yaml",
197
+ "versionInfo": "2.9.0",
198
+ "downloadLocation": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
199
+ "filesAnalyzed": false,
200
+ "licenseConcluded": "NOASSERTION",
201
+ "licenseDeclared": "ISC",
202
+ "copyrightText": "NOASSERTION",
203
+ "supplier": "NOASSERTION",
204
+ "externalRefs": [
205
+ {
206
+ "referenceCategory": "PACKAGE-MANAGER",
207
+ "referenceType": "purl",
208
+ "referenceLocator": "pkg:npm/yaml@2.9.0"
209
+ }
210
+ ],
211
+ "description": "JavaScript parser and stringifier for YAML",
212
+ "homepage": "https://eemeli.org/yaml/"
213
+ },
194
214
  {
195
215
  "SPDXID": "SPDXRef-Package-zod-4.4.3",
196
216
  "name": "zod",
@@ -248,6 +268,11 @@
248
268
  "relatedSpdxElement": "SPDXRef-Package-safe-regex-2.1.1",
249
269
  "relationshipType": "DEPENDS_ON"
250
270
  },
271
+ {
272
+ "spdxElementId": "SPDXRef-RootPackage",
273
+ "relatedSpdxElement": "SPDXRef-Package-yaml-2.9.0",
274
+ "relationshipType": "DEPENDS_ON"
275
+ },
251
276
  {
252
277
  "spdxElementId": "SPDXRef-RootPackage",
253
278
  "relatedSpdxElement": "SPDXRef-Package-zod-4.4.3",