@kici-dev/engine 0.5.0 → 0.6.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/audit/access-log-policy.js +1 -0
- package/dist/audit/retention-policy.js +2 -0
- package/dist/context/held-run-job-id.d.ts +26 -10
- package/dist/context/held-run-job-id.js +30 -11
- package/dist/context/index.d.ts +1 -1
- package/dist/context/index.js +3 -3
- package/dist/context/types.d.ts +10 -1
- package/dist/context/types.js +10 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +14 -11
- package/dist/labels.d.ts +66 -12
- package/dist/labels.js +74 -17
- package/dist/mcp/held-run-resolve.d.ts +40 -0
- package/dist/mcp/held-run-resolve.js +68 -15
- package/dist/mcp/tool-schemas.d.ts +4 -0
- package/dist/mcp/tool-schemas.js +13 -1
- package/dist/metrics/catalog-policy.d.ts +6 -3
- package/dist/metrics/catalog-policy.js +31 -10
- package/dist/metrics/metric-catalog.generated.d.ts +110 -0
- package/dist/metrics/metric-catalog.generated.js +132 -0
- package/dist/protocol/dashboard-global-workflows.js +2 -2
- package/dist/protocol/event-log-payload.js +1 -1
- package/dist/protocol/messages/access-log.d.ts +5 -0
- package/dist/protocol/messages/access-log.js +1 -0
- package/dist/protocol/messages/actor.d.ts +13 -2
- package/dist/protocol/messages/actor.js +16 -5
- package/dist/protocol/messages/common.js +1 -1
- package/dist/protocol/messages/dashboard-global-workflows.d.ts +21 -0
- package/dist/protocol/messages/dashboard-global-workflows.js +28 -1
- package/dist/protocol/messages/dashboard.d.ts +43 -5
- package/dist/protocol/messages/dashboard.js +55 -6
- package/dist/protocol/messages/execution-status.d.ts +41 -0
- package/dist/protocol/messages/execution-status.js +53 -2
- package/dist/protocol/messages/git-credential-relay.d.ts +78 -0
- package/dist/protocol/messages/git-credential-relay.js +86 -0
- package/dist/protocol/messages/orchestrator-agent.d.ts +89 -0
- package/dist/protocol/messages/orchestrator-agent.js +98 -3
- package/dist/protocol/messages/peer.d.ts +7 -0
- package/dist/protocol/messages/peer.js +18 -1
- package/dist/protocol/messages/platform-orchestrator.d.ts +150 -0
- package/dist/protocol/messages/platform-orchestrator.js +164 -19
- package/dist/protocol/version.d.ts +19 -2
- package/dist/protocol/version.js +20 -3
- package/dist/provenance/verify.js +11 -10
- package/dist/provider/check-status-poster.d.ts +24 -3
- package/dist/provider/contributor-resolver.d.ts +11 -3
- package/dist/provider/git-credential.d.ts +77 -0
- package/dist/provider/git-credential.js +10 -0
- package/dist/provider/index.d.ts +2 -0
- package/dist/provider/index.js +2 -1
- package/dist/provider/webhook-normalizer.d.ts +12 -12
- package/dist/repo/pattern-negation.d.ts +73 -0
- package/dist/repo/pattern-negation.js +86 -0
- package/dist/scaler/registry-auth.d.ts +18 -0
- package/dist/scaler/registry-auth.js +28 -0
- package/dist/scaler/scaler-backend-type.d.ts +35 -0
- package/dist/scaler/scaler-backend-type.js +39 -2
- package/dist/scaler/scaler-events.d.ts +79 -0
- package/dist/scaler/scaler-events.js +87 -0
- package/dist/trigger/content-requirements.js +1 -1
- package/dist/trigger/decision-trace.d.ts +79 -0
- package/dist/trigger/decision-trace.js +116 -8
- package/dist/trigger/matcher.js +4 -2
- package/dist/trigger/types.d.ts +87 -7
- package/dist/trigger/types.js +6 -1
- package/dist/ws/rate-limiter.js +3 -3
- package/package.json +11 -3
- package/sbom.spdx.json +15 -15
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
//#region src/repo/pattern-negation.ts
|
|
3
|
+
/**
|
|
4
|
+
* The one classifier for "picomatch would read this repo pattern as a
|
|
5
|
+
* negation".
|
|
6
|
+
*
|
|
7
|
+
* Repo patterns are written on lists whose direction is already fixed by the
|
|
8
|
+
* list itself — a role's allowed repositories, a global-workflow allow list, a
|
|
9
|
+
* global-workflow deny list. A pattern picomatch reads as a negation inverts
|
|
10
|
+
* that direction inside a single entry, so an entry that reads as a restriction
|
|
11
|
+
* matches as its complement. On an allow list that grants almost everything; on
|
|
12
|
+
* a deny list it admits the one repository the entry named.
|
|
13
|
+
*
|
|
14
|
+
* This lives in the engine, and not beside either consumer, because two
|
|
15
|
+
* hand-maintained ban lists for one pattern language cannot be kept in step.
|
|
16
|
+
* The two ways they drift apart are both live hazards: a list that misses the
|
|
17
|
+
* regular-expression assertions accepts a real inversion, and a list that
|
|
18
|
+
* refuses `[!…]` turns away a genuine restriction. Every surface that stores a
|
|
19
|
+
* repo pattern reads its verdict from here, so neither can happen on one
|
|
20
|
+
* surface alone.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* The regular-expression negations picomatch passes through into the compiled
|
|
24
|
+
* matcher: the negative lookahead `(?!…)` and the negative lookbehind `(?<!…)`.
|
|
25
|
+
* The optional `<` is what makes one pattern cover both; the positive `(?=…)`,
|
|
26
|
+
* `(?<=…)`, `(?:…)` and a plain capture group are deliberately not matched.
|
|
27
|
+
*/
|
|
28
|
+
const REGEX_NEGATIVE_ASSERTION = /\(\?<?!/;
|
|
29
|
+
/**
|
|
30
|
+
* Why picomatch would read `pattern` as a negation, or null when it would not.
|
|
31
|
+
*
|
|
32
|
+
* Four arms, because picomatch reads four negation forms, each of which turns a
|
|
33
|
+
* pattern that reads as a restriction into a grant.
|
|
34
|
+
*
|
|
35
|
+
* A leading `!` negates the whole pattern. The extglob complement `!(…)`
|
|
36
|
+
* negates wherever it appears, so `org/!(secret)` covers every repository under
|
|
37
|
+
* `org/` except that one — the same defect in a prefix-scoped shape. The
|
|
38
|
+
* negated character class `[^…]` does it one character at a time: `org/[^s]*`
|
|
39
|
+
* covers every repository under `org/` whose name does not begin with `s`. The
|
|
40
|
+
* negative assertions are the widest of the four, because they can spell a
|
|
41
|
+
* whole repository identifier rather than one character: picomatch compiles a
|
|
42
|
+
* pattern to a regular expression and passes a group it does not recognise
|
|
43
|
+
* through verbatim, so `(?!org/secret)**` reaches the matcher as a real
|
|
44
|
+
* lookahead and matches every repository in every organization except the one
|
|
45
|
+
* it names.
|
|
46
|
+
*
|
|
47
|
+
* The extglob arm matches the two-character sequence `!(` and nothing wider:
|
|
48
|
+
* `*(`, `+(`, `@(` and `?(` are the non-complementing extglob heads and do not
|
|
49
|
+
* invert, so rejecting a bare `(` would refuse four harmless forms for no gain.
|
|
50
|
+
* A `(` cannot appear in a repository identifier, so no legitimate pattern
|
|
51
|
+
* contains `!(`. The assertion arm is narrow for the same reason: `(?=…)`,
|
|
52
|
+
* `(?<=…)`, `(?:…)` and a plain capture group match strictly what they name, so
|
|
53
|
+
* only the two negative forms are refused.
|
|
54
|
+
*
|
|
55
|
+
* The character-class arm matches `[^` and nothing wider: a `[` cannot appear
|
|
56
|
+
* in a repository identifier either, and a bracket that is not a negation —
|
|
57
|
+
* `org/[abc]*` — is a legitimate restriction. It matches `[^` and NOT `[!`:
|
|
58
|
+
* picomatch does not read `[!…]` as the POSIX negation. It reads it as a
|
|
59
|
+
* literal class containing `!` and the listed characters, so `org/[!s]*`
|
|
60
|
+
* matches exactly the repositories whose name begins with `!` or `s` — the
|
|
61
|
+
* exact inverse of `org/[^s]*`, and a genuine restriction. Rejecting it would
|
|
62
|
+
* refuse a pattern that grants strictly less than it names while leaving the
|
|
63
|
+
* real inversion open.
|
|
64
|
+
*
|
|
65
|
+
* A bare `!` is different — it *can* appear in a repository name — so the first
|
|
66
|
+
* arm stays anchored: `org/we!rd` is a legitimate literal, and an
|
|
67
|
+
* `includes('!')` check would silently reject a valid repository name.
|
|
68
|
+
*/
|
|
69
|
+
function negatedPatternReason(pattern) {
|
|
70
|
+
if (pattern.startsWith("!")) return "negation ('!' prefix)";
|
|
71
|
+
if (pattern.includes("!(")) return "extglob negation ('!(…)')";
|
|
72
|
+
if (pattern.includes("[^")) return "character-class negation ('[^…]')";
|
|
73
|
+
if (REGEX_NEGATIVE_ASSERTION.test(pattern)) return "negative assertion ('(?!…)' / '(?<!…)')";
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* True when `pattern` is a picomatch negation — whole-pattern `!…`, extglob
|
|
78
|
+
* `!(…)`, negated character class `[^…]`, or a negative lookahead / lookbehind.
|
|
79
|
+
*/
|
|
80
|
+
function isNegatedPattern(pattern) {
|
|
81
|
+
return negatedPatternReason(pattern) !== null;
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
export { REGEX_NEGATIVE_ASSERTION, isNegatedPattern, negatedPatternReason };
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=pattern-negation.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry-host extraction for container image references.
|
|
3
|
+
*
|
|
4
|
+
* Lives in the engine because three sites need the same answer: the agent's
|
|
5
|
+
* container sandbox, the container scaler backend, and the bare-metal backend
|
|
6
|
+
* in container mode. A private pull authenticates against the registry HOST,
|
|
7
|
+
* which is not a field of the image ref — it has to be derived from it.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Registry host (a container runtime's `authconfig.serveraddress`) for an image ref.
|
|
11
|
+
*
|
|
12
|
+
* Docker's own rule: the first path segment is the registry only when it looks
|
|
13
|
+
* like a host — it contains a dot or a colon, or it is exactly `localhost`.
|
|
14
|
+
* Otherwise the ref is a Docker Hub short name (`nginx`, `acme/ci`) and the
|
|
15
|
+
* registry is `docker.io`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function registryHostFromImageRef(image: string): string;
|
|
18
|
+
//# sourceMappingURL=registry-auth.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
//#region src/scaler/registry-auth.ts
|
|
3
|
+
/**
|
|
4
|
+
* Registry-host extraction for container image references.
|
|
5
|
+
*
|
|
6
|
+
* Lives in the engine because three sites need the same answer: the agent's
|
|
7
|
+
* container sandbox, the container scaler backend, and the bare-metal backend
|
|
8
|
+
* in container mode. A private pull authenticates against the registry HOST,
|
|
9
|
+
* which is not a field of the image ref — it has to be derived from it.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Registry host (a container runtime's `authconfig.serveraddress`) for an image ref.
|
|
13
|
+
*
|
|
14
|
+
* Docker's own rule: the first path segment is the registry only when it looks
|
|
15
|
+
* like a host — it contains a dot or a colon, or it is exactly `localhost`.
|
|
16
|
+
* Otherwise the ref is a Docker Hub short name (`nginx`, `acme/ci`) and the
|
|
17
|
+
* registry is `docker.io`.
|
|
18
|
+
*/
|
|
19
|
+
function registryHostFromImageRef(image) {
|
|
20
|
+
const firstSlash = image.indexOf("/");
|
|
21
|
+
if (firstSlash === -1) return "docker.io";
|
|
22
|
+
const candidate = image.slice(0, firstSlash);
|
|
23
|
+
return candidate.includes(".") || candidate.includes(":") || candidate === "localhost" ? candidate : "docker.io";
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { registryHostFromImageRef };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=registry-auth.js.map
|
|
@@ -9,8 +9,43 @@ import { z } from 'zod';
|
|
|
9
9
|
export declare const ScalerBackendType: z.ZodEnum<{
|
|
10
10
|
"bare-metal": "bare-metal";
|
|
11
11
|
container: "container";
|
|
12
|
+
event: "event";
|
|
12
13
|
firecracker: "firecracker";
|
|
13
14
|
kubernetes: "kubernetes";
|
|
14
15
|
}>;
|
|
15
16
|
export type ScalerBackendType = z.infer<typeof ScalerBackendType>;
|
|
17
|
+
/**
|
|
18
|
+
* Reserved event-name prefix for KiCI-internal system events (today: the event
|
|
19
|
+
* scaler's scale-up / scale-down events). Custom events emitted from user
|
|
20
|
+
* workflow steps (`ctx.emit(...)`) MUST NOT use this prefix — both the SDK
|
|
21
|
+
* (client-side) and the orchestrator (authoritative) reject a name that starts
|
|
22
|
+
* with it, so a user step cannot forge a system event. The rate limiter also
|
|
23
|
+
* exempts it. Defined in `@kici-dev/engine` so the SDK and the orchestrator
|
|
24
|
+
* share one source of truth without the SDK importing the orchestrator.
|
|
25
|
+
*/
|
|
26
|
+
export declare const KICI_EVENT_NAME_PREFIX = "kici.";
|
|
27
|
+
/**
|
|
28
|
+
* Reserved event-name prefix for the events the ORCHESTRATOR mints for itself
|
|
29
|
+
* (`__schedule_fire`, `__workflow_complete`, `__job_complete`,
|
|
30
|
+
* `__workflows_failed_batch`).
|
|
31
|
+
*
|
|
32
|
+
* Reserved for the same reason as {@link KICI_EVENT_NAME_PREFIX}, and more
|
|
33
|
+
* sharply: every name under this prefix is exempt from the event-storm rate
|
|
34
|
+
* limiter, and `__schedule_fire` is additionally classified as a TRUSTED ref —
|
|
35
|
+
* no run causes it, so nothing external shaped it. The other three ARE caused
|
|
36
|
+
* by runs and inherit the tier of the run (or, for the failure batch, the most
|
|
37
|
+
* restrictive tier across the runs) behind them, so they forge no privilege on
|
|
38
|
+
* their own. A user step that could emit any of them would forge the
|
|
39
|
+
* rate-limiter exemption, and `__schedule_fire` the trusted classification on
|
|
40
|
+
* top of it — so the same two-sided reservation applies to the whole prefix,
|
|
41
|
+
* SDK first and orchestrator authoritatively.
|
|
42
|
+
*/
|
|
43
|
+
export declare const INTERNAL_EVENT_NAME_PREFIX = "__";
|
|
44
|
+
/**
|
|
45
|
+
* The reserved prefix `eventName` uses, or `undefined` when a user step may
|
|
46
|
+
* emit it. One definition of "reserved", so the SDK-side check and the
|
|
47
|
+
* orchestrator's authoritative backstop can never disagree about which names a
|
|
48
|
+
* workflow may emit.
|
|
49
|
+
*/
|
|
50
|
+
export declare function reservedEventNamePrefix(eventName: string): string | undefined;
|
|
16
51
|
//# sourceMappingURL=scaler-backend-type.d.ts.map
|
|
@@ -12,9 +12,46 @@ const ScalerBackendType = z.enum([
|
|
|
12
12
|
"container",
|
|
13
13
|
"bare-metal",
|
|
14
14
|
"firecracker",
|
|
15
|
-
"kubernetes"
|
|
15
|
+
"kubernetes",
|
|
16
|
+
"event"
|
|
16
17
|
]);
|
|
18
|
+
/**
|
|
19
|
+
* Reserved event-name prefix for KiCI-internal system events (today: the event
|
|
20
|
+
* scaler's scale-up / scale-down events). Custom events emitted from user
|
|
21
|
+
* workflow steps (`ctx.emit(...)`) MUST NOT use this prefix — both the SDK
|
|
22
|
+
* (client-side) and the orchestrator (authoritative) reject a name that starts
|
|
23
|
+
* with it, so a user step cannot forge a system event. The rate limiter also
|
|
24
|
+
* exempts it. Defined in `@kici-dev/engine` so the SDK and the orchestrator
|
|
25
|
+
* share one source of truth without the SDK importing the orchestrator.
|
|
26
|
+
*/
|
|
27
|
+
const KICI_EVENT_NAME_PREFIX = "kici.";
|
|
28
|
+
/**
|
|
29
|
+
* Reserved event-name prefix for the events the ORCHESTRATOR mints for itself
|
|
30
|
+
* (`__schedule_fire`, `__workflow_complete`, `__job_complete`,
|
|
31
|
+
* `__workflows_failed_batch`).
|
|
32
|
+
*
|
|
33
|
+
* Reserved for the same reason as {@link KICI_EVENT_NAME_PREFIX}, and more
|
|
34
|
+
* sharply: every name under this prefix is exempt from the event-storm rate
|
|
35
|
+
* limiter, and `__schedule_fire` is additionally classified as a TRUSTED ref —
|
|
36
|
+
* no run causes it, so nothing external shaped it. The other three ARE caused
|
|
37
|
+
* by runs and inherit the tier of the run (or, for the failure batch, the most
|
|
38
|
+
* restrictive tier across the runs) behind them, so they forge no privilege on
|
|
39
|
+
* their own. A user step that could emit any of them would forge the
|
|
40
|
+
* rate-limiter exemption, and `__schedule_fire` the trusted classification on
|
|
41
|
+
* top of it — so the same two-sided reservation applies to the whole prefix,
|
|
42
|
+
* SDK first and orchestrator authoritatively.
|
|
43
|
+
*/
|
|
44
|
+
const INTERNAL_EVENT_NAME_PREFIX = "__";
|
|
45
|
+
/**
|
|
46
|
+
* The reserved prefix `eventName` uses, or `undefined` when a user step may
|
|
47
|
+
* emit it. One definition of "reserved", so the SDK-side check and the
|
|
48
|
+
* orchestrator's authoritative backstop can never disagree about which names a
|
|
49
|
+
* workflow may emit.
|
|
50
|
+
*/
|
|
51
|
+
function reservedEventNamePrefix(eventName) {
|
|
52
|
+
for (const prefix of [KICI_EVENT_NAME_PREFIX, "__"]) if (eventName.startsWith(prefix)) return prefix;
|
|
53
|
+
}
|
|
17
54
|
//#endregion
|
|
18
|
-
export { ScalerBackendType };
|
|
55
|
+
export { INTERNAL_EVENT_NAME_PREFIX, KICI_EVENT_NAME_PREFIX, ScalerBackendType, reservedEventNamePrefix };
|
|
19
56
|
|
|
20
57
|
//# sourceMappingURL=scaler-backend-type.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared schemas and constants for the event scaler backend.
|
|
3
|
+
*
|
|
4
|
+
* The event scaler performs no local compute: its `spawn()` / `destroy()` emit
|
|
5
|
+
* reserved `kici.`-prefixed custom events (`kici.scaler.scale-up` /
|
|
6
|
+
* `kici.scaler.scale-down`) that a customer-authored provisioning / teardown
|
|
7
|
+
* workflow consumes via the `kiciEvent()` trigger. These schemas define the
|
|
8
|
+
* event payloads and the single source of truth for the reserved event names.
|
|
9
|
+
*
|
|
10
|
+
* They live here, not in the orchestrator, because both sides of the contract
|
|
11
|
+
* need them: the orchestrator emits, and a workflow file — which may import
|
|
12
|
+
* `@kici-dev/sdk` and nothing else — consumes. Engine is the only package both
|
|
13
|
+
* the SDK and the orchestrator depend on, so it is the shared floor. The SDK
|
|
14
|
+
* re-exports all four symbols; the orchestrator's `scaler/scaler-events.ts`
|
|
15
|
+
* re-exports them for its own call sites.
|
|
16
|
+
*/
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
/**
|
|
19
|
+
* Reserved event names the event scaler emits. Both start with
|
|
20
|
+
* `KICI_EVENT_NAME_PREFIX` (`./scaler-backend-type.ts`), so the rate limiter
|
|
21
|
+
* exempts them and user steps cannot forge them — `scaler-events.test.ts`
|
|
22
|
+
* asserts that relationship holds for both names.
|
|
23
|
+
*/
|
|
24
|
+
export declare const SCALER_EVENT_NAMES: {
|
|
25
|
+
readonly scaleUp: 'kici.scaler.scale-up';
|
|
26
|
+
readonly scaleDown: 'kici.scaler.scale-down';
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Why the scaler asked for an agent to be torn down. Carried on the
|
|
30
|
+
* `kici.scaler.scale-down` event so a teardown workflow (and the timeline) can
|
|
31
|
+
* distinguish an idle reap from a job-complete teardown or a spawn timeout.
|
|
32
|
+
*/
|
|
33
|
+
export declare const ScaleDownReason: z.ZodEnum<{
|
|
34
|
+
drain: "drain";
|
|
35
|
+
"heartbeat-timeout": "heartbeat-timeout";
|
|
36
|
+
idle: "idle";
|
|
37
|
+
"job-complete": "job-complete";
|
|
38
|
+
shutdown: "shutdown";
|
|
39
|
+
"spawn-timeout": "spawn-timeout";
|
|
40
|
+
}>;
|
|
41
|
+
export type ScaleDownReason = z.infer<typeof ScaleDownReason>;
|
|
42
|
+
/**
|
|
43
|
+
* Payload of a `kici.scaler.scale-up` event. Everything a provisioning workflow
|
|
44
|
+
* needs to boot an instance whose agent registers back with `agentId` and claim
|
|
45
|
+
* its ephemeral credentials with `claimCode`. The ephemeral token itself is
|
|
46
|
+
* NEVER in this payload — it is delivered only via the
|
|
47
|
+
* `scaler.claim-credentials` RPC response.
|
|
48
|
+
*/
|
|
49
|
+
export declare const ScalerScaleUpPayload: z.ZodObject<{
|
|
50
|
+
scalerName: z.ZodString;
|
|
51
|
+
agentId: z.ZodString;
|
|
52
|
+
labels: z.ZodArray<z.ZodString>;
|
|
53
|
+
mandatoryLabels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
54
|
+
resources: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
55
|
+
orchestratorUrl: z.ZodString;
|
|
56
|
+
claimCode: z.ZodString;
|
|
57
|
+
jobId: z.ZodOptional<z.ZodString>;
|
|
58
|
+
requestId: z.ZodString;
|
|
59
|
+
}, z.core.$strip>;
|
|
60
|
+
export type ScalerScaleUpPayload = z.infer<typeof ScalerScaleUpPayload>;
|
|
61
|
+
/**
|
|
62
|
+
* Payload of a `kici.scaler.scale-down` event. A teardown workflow deletes the
|
|
63
|
+
* instance registered under `agentId`.
|
|
64
|
+
*/
|
|
65
|
+
export declare const ScalerScaleDownPayload: z.ZodObject<{
|
|
66
|
+
scalerName: z.ZodString;
|
|
67
|
+
agentId: z.ZodString;
|
|
68
|
+
reason: z.ZodEnum<{
|
|
69
|
+
drain: "drain";
|
|
70
|
+
"heartbeat-timeout": "heartbeat-timeout";
|
|
71
|
+
idle: "idle";
|
|
72
|
+
"job-complete": "job-complete";
|
|
73
|
+
shutdown: "shutdown";
|
|
74
|
+
"spawn-timeout": "spawn-timeout";
|
|
75
|
+
}>;
|
|
76
|
+
requestId: z.ZodString;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
export type ScalerScaleDownPayload = z.infer<typeof ScalerScaleDownPayload>;
|
|
79
|
+
//# sourceMappingURL=scaler-events.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/scaler/scaler-events.ts
|
|
4
|
+
/**
|
|
5
|
+
* Shared schemas and constants for the event scaler backend.
|
|
6
|
+
*
|
|
7
|
+
* The event scaler performs no local compute: its `spawn()` / `destroy()` emit
|
|
8
|
+
* reserved `kici.`-prefixed custom events (`kici.scaler.scale-up` /
|
|
9
|
+
* `kici.scaler.scale-down`) that a customer-authored provisioning / teardown
|
|
10
|
+
* workflow consumes via the `kiciEvent()` trigger. These schemas define the
|
|
11
|
+
* event payloads and the single source of truth for the reserved event names.
|
|
12
|
+
*
|
|
13
|
+
* They live here, not in the orchestrator, because both sides of the contract
|
|
14
|
+
* need them: the orchestrator emits, and a workflow file — which may import
|
|
15
|
+
* `@kici-dev/sdk` and nothing else — consumes. Engine is the only package both
|
|
16
|
+
* the SDK and the orchestrator depend on, so it is the shared floor. The SDK
|
|
17
|
+
* re-exports all four symbols; the orchestrator's `scaler/scaler-events.ts`
|
|
18
|
+
* re-exports them for its own call sites.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Reserved event names the event scaler emits. Both start with
|
|
22
|
+
* `KICI_EVENT_NAME_PREFIX` (`./scaler-backend-type.ts`), so the rate limiter
|
|
23
|
+
* exempts them and user steps cannot forge them — `scaler-events.test.ts`
|
|
24
|
+
* asserts that relationship holds for both names.
|
|
25
|
+
*/
|
|
26
|
+
const SCALER_EVENT_NAMES = {
|
|
27
|
+
scaleUp: "kici.scaler.scale-up",
|
|
28
|
+
scaleDown: "kici.scaler.scale-down"
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Why the scaler asked for an agent to be torn down. Carried on the
|
|
32
|
+
* `kici.scaler.scale-down` event so a teardown workflow (and the timeline) can
|
|
33
|
+
* distinguish an idle reap from a job-complete teardown or a spawn timeout.
|
|
34
|
+
*/
|
|
35
|
+
const ScaleDownReason = z.enum([
|
|
36
|
+
"idle",
|
|
37
|
+
"job-complete",
|
|
38
|
+
"heartbeat-timeout",
|
|
39
|
+
"spawn-timeout",
|
|
40
|
+
"drain",
|
|
41
|
+
"shutdown"
|
|
42
|
+
]);
|
|
43
|
+
/**
|
|
44
|
+
* Payload of a `kici.scaler.scale-up` event. Everything a provisioning workflow
|
|
45
|
+
* needs to boot an instance whose agent registers back with `agentId` and claim
|
|
46
|
+
* its ephemeral credentials with `claimCode`. The ephemeral token itself is
|
|
47
|
+
* NEVER in this payload — it is delivered only via the
|
|
48
|
+
* `scaler.claim-credentials` RPC response.
|
|
49
|
+
*/
|
|
50
|
+
const ScalerScaleUpPayload = z.object({
|
|
51
|
+
/** Name of the scaler entry that emitted the event. */
|
|
52
|
+
scalerName: z.string(),
|
|
53
|
+
/** Agent id the provisioned instance must register with (correlates the spawn). */
|
|
54
|
+
agentId: z.string(),
|
|
55
|
+
/** Exact label set the pending job needs. */
|
|
56
|
+
labels: z.array(z.string()),
|
|
57
|
+
/** Mandatory (taint) labels the pool gates on, if any. */
|
|
58
|
+
mandatoryLabels: z.array(z.string()).default([]),
|
|
59
|
+
/** Resolved resource hints for the provision (e.g. cpus / memBytes). */
|
|
60
|
+
resources: z.record(z.string(), z.unknown()).default({}),
|
|
61
|
+
/** Orchestrator WS URL the provisioned agent connects back to. */
|
|
62
|
+
orchestratorUrl: z.string(),
|
|
63
|
+
/** Single-use code the workflow exchanges for ephemeral agent credentials. */
|
|
64
|
+
claimCode: z.string(),
|
|
65
|
+
/** Execution job id the spawn is bound to (absent for unbound / warm spawns). */
|
|
66
|
+
jobId: z.string().optional(),
|
|
67
|
+
/** Correlation id for this scale-up request. */
|
|
68
|
+
requestId: z.string()
|
|
69
|
+
});
|
|
70
|
+
/**
|
|
71
|
+
* Payload of a `kici.scaler.scale-down` event. A teardown workflow deletes the
|
|
72
|
+
* instance registered under `agentId`.
|
|
73
|
+
*/
|
|
74
|
+
const ScalerScaleDownPayload = z.object({
|
|
75
|
+
/** Name of the scaler entry that emitted the event. */
|
|
76
|
+
scalerName: z.string(),
|
|
77
|
+
/** Agent id whose instance should be torn down. */
|
|
78
|
+
agentId: z.string(),
|
|
79
|
+
/** Why the teardown was requested. */
|
|
80
|
+
reason: ScaleDownReason,
|
|
81
|
+
/** Correlation id for this scale-down request. */
|
|
82
|
+
requestId: z.string()
|
|
83
|
+
});
|
|
84
|
+
//#endregion
|
|
85
|
+
export { SCALER_EVENT_NAMES, ScaleDownReason, ScalerScaleDownPayload, ScalerScaleUpPayload };
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=scaler-events.js.map
|
|
@@ -19,7 +19,7 @@ import { parse } from "yaml";
|
|
|
19
19
|
* of the browser-facing engine barrel (`src/index.ts`).
|
|
20
20
|
*/
|
|
21
21
|
/** Hard byte cap enforced before any parse: an oversize file is indeterminate. */
|
|
22
|
-
const MAX_CONTENT_BYTES =
|
|
22
|
+
const MAX_CONTENT_BYTES = 1048576;
|
|
23
23
|
/**
|
|
24
24
|
* Anchor/alias expansion cap for YAML parsing. The `yaml` library's default of
|
|
25
25
|
* 100 does not reject a small billion-laughs bomb; 50 rejects it while staying
|
|
@@ -31,8 +31,28 @@ export interface WorkflowDecision {
|
|
|
31
31
|
/** Summary reason */
|
|
32
32
|
summary: string;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Max characters of any free-text field a trace entry carries, so an
|
|
36
|
+
* essay-length input stays bounded.
|
|
37
|
+
*
|
|
38
|
+
* The entry count is capped separately, downstream, but a count bound alone
|
|
39
|
+
* bounds nothing: a single `paths` entry names every changed file in the push
|
|
40
|
+
* and a single `bodyMatch` entry quotes a comment body an outsider authored, so
|
|
41
|
+
* fifty entries can still be megabytes. The forwarded trace rides one WebSocket
|
|
42
|
+
* frame to the Platform, and a frame past the server's payload ceiling closes
|
|
43
|
+
* the connection — stalling every delivery for that organization until it
|
|
44
|
+
* reconnects. Bounding at the point each field is minted is what makes the
|
|
45
|
+
* downstream size guards a backstop rather than the only limit.
|
|
46
|
+
*/
|
|
47
|
+
export declare const TRACE_TEXT_MAX = 200;
|
|
48
|
+
/** Clamp one free-text trace field, marking a clamped value with an ellipsis. */
|
|
49
|
+
export declare function truncateTraceText(text: string): string;
|
|
34
50
|
/**
|
|
35
51
|
* Create a new trace entry.
|
|
52
|
+
*
|
|
53
|
+
* Every free-text field is clamped here rather than at each call site: the
|
|
54
|
+
* fields are fed from event content of unbounded size, and one unclamped call
|
|
55
|
+
* site is enough to reintroduce an unbounded frame.
|
|
36
56
|
*/
|
|
37
57
|
export declare function createTraceEntry(check: string, pattern: string, value: string, passed: boolean, reason?: string): TraceEntry;
|
|
38
58
|
/**
|
|
@@ -54,6 +74,8 @@ export declare const TraceCheck: {
|
|
|
54
74
|
readonly GlobalFilter: 'filter';
|
|
55
75
|
/** Tier-0 declarative `commitMessage` filter, read from the normalized event. */
|
|
56
76
|
readonly CommitMessage: 'commitMessage';
|
|
77
|
+
/** Materialization of a matched workflow's jobs, on the way to dispatch. */
|
|
78
|
+
readonly Dispatch: 'dispatch';
|
|
57
79
|
};
|
|
58
80
|
export type TraceCheck = (typeof TraceCheck)[keyof typeof TraceCheck];
|
|
59
81
|
/**
|
|
@@ -96,6 +118,16 @@ export declare function createGlobalFilterTraceEntry(args: {
|
|
|
96
118
|
indeterminate?: boolean;
|
|
97
119
|
reason?: string;
|
|
98
120
|
}): TraceEntry;
|
|
121
|
+
/**
|
|
122
|
+
* Record that a matched workflow could not be materialized into jobs.
|
|
123
|
+
*
|
|
124
|
+
* A workflow whose triggers matched and whose build then threw is absent from
|
|
125
|
+
* every other record: no run row is created and no job is queued. Omitting it
|
|
126
|
+
* from the trace as well leaves its author unable to tell it apart from a
|
|
127
|
+
* workflow that was never registered — the exact indistinguishability the trace
|
|
128
|
+
* exists to remove.
|
|
129
|
+
*/
|
|
130
|
+
export declare function createDispatchFailureTraceEntry(reason: string): TraceEntry;
|
|
99
131
|
/**
|
|
100
132
|
* Return a copy of `decision` with `entries` appended to its checks.
|
|
101
133
|
*
|
|
@@ -123,6 +155,53 @@ export declare function createCommitMessageTraceEntry(args: {
|
|
|
123
155
|
indeterminate?: boolean;
|
|
124
156
|
reason?: string;
|
|
125
157
|
}): TraceEntry;
|
|
158
|
+
/**
|
|
159
|
+
* What a withheld trace field is replaced with when the reader does not hold
|
|
160
|
+
* `event_log:read_payload`.
|
|
161
|
+
*
|
|
162
|
+
* Lives here rather than beside the Platform's redactor because three packages
|
|
163
|
+
* read it: the Platform writes it, the dashboard renders it, and the E2E suite
|
|
164
|
+
* asserts the permission boundary against it. A literal repeated at each site
|
|
165
|
+
* would drift into a marker one of them no longer recognizes.
|
|
166
|
+
*/
|
|
167
|
+
export declare const REDACTED_TRACE_FIELD = "[redacted \u2014 requires event_log:read_payload]";
|
|
168
|
+
/**
|
|
169
|
+
* Workflow name the truncation marker carries.
|
|
170
|
+
*
|
|
171
|
+
* A reserved sentinel rather than a real workflow: it is written by one package
|
|
172
|
+
* and read back by two others, so a literal repeated at each site would drift
|
|
173
|
+
* into a marker nobody recognizes.
|
|
174
|
+
*/
|
|
175
|
+
export declare const TRACE_TRUNCATION_WORKFLOW_NAME = "(trace truncated)";
|
|
176
|
+
/**
|
|
177
|
+
* Build the marker that stands in for the decisions a size budget dropped.
|
|
178
|
+
*
|
|
179
|
+
* The trace is truncated rather than discarded: a reader who is told nothing
|
|
180
|
+
* cannot tell "matching never ran" from "the trace was too large to keep", and
|
|
181
|
+
* those two have opposite answers to "why did my workflow not fire".
|
|
182
|
+
*/
|
|
183
|
+
export declare function createTraceTruncationMarker(omitted: number): Record<string, unknown>;
|
|
184
|
+
/**
|
|
185
|
+
* UTF-8 byte length of `text`.
|
|
186
|
+
*
|
|
187
|
+
* Byte length, never `String.length`: the latter counts UTF-16 code units, so a
|
|
188
|
+
* CJK-heavy comment body measures at roughly a third of the bytes it actually
|
|
189
|
+
* costs on the wire and in the stored row.
|
|
190
|
+
*/
|
|
191
|
+
export declare function utf8ByteLength(text: string): number;
|
|
192
|
+
/**
|
|
193
|
+
* Drop trailing decisions until the serialized array fits `maxBytes`, appending
|
|
194
|
+
* a marker naming how many were dropped.
|
|
195
|
+
*
|
|
196
|
+
* Shared by the orchestrator, which bounds what it puts on the wire, and the
|
|
197
|
+
* Platform, which bounds what it stores. Two independent budgets over one
|
|
198
|
+
* shape: keeping one implementation is what stops them from disagreeing about
|
|
199
|
+
* what a truncated trace looks like.
|
|
200
|
+
*/
|
|
201
|
+
export declare function truncateDecisionsToByteBudget<T>(decisions: readonly T[], maxBytes: number): {
|
|
202
|
+
decisions: Array<T | Record<string, unknown>>;
|
|
203
|
+
omitted: number;
|
|
204
|
+
};
|
|
126
205
|
/**
|
|
127
206
|
* Create a workflow decision record.
|
|
128
207
|
*/
|