@kici-dev/shared 0.0.0 → 0.1.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 (105) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +1 -6
  3. package/dist/chunk-gOLHoazu.js +4 -0
  4. package/dist/cold-store/bucket.d.ts +26 -0
  5. package/dist/cold-store/bucket.js +40 -0
  6. package/dist/cold-store/bucket.test.d.ts +2 -0
  7. package/dist/cold-store/chunk-encoder.d.ts +63 -0
  8. package/dist/cold-store/chunk-encoder.js +94 -0
  9. package/dist/cold-store/chunk-encoder.test.d.ts +2 -0
  10. package/dist/cold-store/chunk-id.d.ts +10 -0
  11. package/dist/cold-store/chunk-id.js +29 -0
  12. package/dist/cold-store/chunk-id.test.d.ts +2 -0
  13. package/dist/cold-store/cold-store.d.ts +345 -0
  14. package/dist/cold-store/cold-store.js +1078 -0
  15. package/dist/cold-store/cold-store.test.d.ts +2 -0
  16. package/dist/cold-store/config.d.ts +62 -0
  17. package/dist/cold-store/config.js +28 -0
  18. package/dist/cold-store/index.d.ts +20 -0
  19. package/dist/cold-store/index.js +11 -0
  20. package/dist/cold-store/key.d.ts +89 -0
  21. package/dist/cold-store/key.js +88 -0
  22. package/dist/cold-store/key.test.d.ts +2 -0
  23. package/dist/cold-store/lru.d.ts +33 -0
  24. package/dist/cold-store/lru.js +59 -0
  25. package/dist/cold-store/lru.test.d.ts +2 -0
  26. package/dist/cold-store/manifest.d.ts +11 -0
  27. package/dist/cold-store/manifest.js +57 -0
  28. package/dist/cold-store/manifest.test.d.ts +2 -0
  29. package/dist/cold-store/metrics.d.ts +103 -0
  30. package/dist/cold-store/metrics.js +179 -0
  31. package/dist/cold-store/table-adapter.d.ts +229 -0
  32. package/dist/cold-store/table-adapter.js +2 -0
  33. package/dist/cold-store/types.d.ts +111 -0
  34. package/dist/cold-store/types.js +2 -0
  35. package/dist/crypto.d.ts +33 -0
  36. package/dist/crypto.js +67 -0
  37. package/dist/db-admin.d.ts +1422 -0
  38. package/dist/db-admin.js +2525 -0
  39. package/dist/db-admin.test.d.ts +2 -0
  40. package/dist/db.d.ts +14 -0
  41. package/dist/db.js +23 -0
  42. package/dist/env/allowlist.d.ts +79 -0
  43. package/dist/env/allowlist.js +86 -0
  44. package/dist/env/define-env.d.ts +172 -0
  45. package/dist/env/define-env.js +295 -0
  46. package/dist/env/define-env.test.d.ts +2 -0
  47. package/dist/env/env-rule-allowlist.test.d.ts +2 -0
  48. package/dist/env/index.d.ts +11 -0
  49. package/dist/env/index.js +4 -0
  50. package/dist/env/logger-env.d.ts +30 -0
  51. package/dist/env/logger-env.js +95 -0
  52. package/dist/error.d.ts +16 -0
  53. package/dist/error.js +58 -0
  54. package/dist/error.test.d.ts +2 -0
  55. package/dist/format-bytes.d.ts +5 -0
  56. package/dist/format-bytes.js +15 -0
  57. package/dist/format-bytes.test.d.ts +2 -0
  58. package/dist/format-duration.d.ts +11 -0
  59. package/dist/format-duration.js +32 -0
  60. package/dist/format-duration.test.d.ts +2 -0
  61. package/dist/graceful-shutdown.d.ts +58 -0
  62. package/dist/graceful-shutdown.js +71 -0
  63. package/dist/graceful-shutdown.test.d.ts +2 -0
  64. package/dist/idempotency-files.d.ts +113 -0
  65. package/dist/idempotency-files.js +190 -0
  66. package/dist/idempotency-files.test.d.ts +2 -0
  67. package/dist/idempotency.d.ts +46 -0
  68. package/dist/idempotency.js +45 -0
  69. package/dist/idempotency.test.d.ts +2 -0
  70. package/dist/index.d.ts +19 -0
  71. package/dist/index.js +31 -0
  72. package/dist/logger.d.ts +57 -0
  73. package/dist/logger.js +175 -0
  74. package/dist/logger.test.d.ts +2 -0
  75. package/dist/reconnect-delay.d.ts +11 -0
  76. package/dist/reconnect-delay.js +22 -0
  77. package/dist/request-context.d.ts +42 -0
  78. package/dist/request-context.js +37 -0
  79. package/dist/ring-buffer.d.ts +35 -0
  80. package/dist/ring-buffer.js +62 -0
  81. package/dist/ring-buffer.test.d.ts +2 -0
  82. package/dist/routes/health.d.ts +26 -0
  83. package/dist/routes/health.js +45 -0
  84. package/dist/routes/health.test.d.ts +2 -0
  85. package/dist/routes/metrics.d.ts +20 -0
  86. package/dist/routes/metrics.js +38 -0
  87. package/dist/s3-client.d.ts +42 -0
  88. package/dist/s3-client.js +25 -0
  89. package/dist/telemetry/index.d.ts +3 -0
  90. package/dist/telemetry/index.js +4 -0
  91. package/dist/telemetry/init.d.ts +20 -0
  92. package/dist/telemetry/init.js +42 -0
  93. package/dist/telemetry/init.test.d.ts +2 -0
  94. package/dist/telemetry/metrics.d.ts +8 -0
  95. package/dist/telemetry/metrics.js +16 -0
  96. package/dist/tool-check.d.ts +33 -0
  97. package/dist/tool-check.js +60 -0
  98. package/dist/tool-check.test.d.ts +2 -0
  99. package/dist/ts-loader-hook.d.ts +26 -0
  100. package/dist/ts-loader-hook.js +48 -0
  101. package/dist/zx.d.ts +8 -0
  102. package/dist/zx.js +78 -0
  103. package/package.json +69 -5
  104. package/sbom.spdx.json +8775 -0
  105. package/index.js +0 -3
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=db-admin.test.d.ts.map
package/dist/db.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import pg from 'pg';
2
+ import { Kysely } from 'kysely';
3
+ /**
4
+ * Create PostgreSQL connection pool.
5
+ */
6
+ export declare function createPool(databaseUrl: string): pg.Pool;
7
+ /**
8
+ * Create Kysely database instance (PostgreSQL only).
9
+ *
10
+ * Generic over the database type so each consumer can provide
11
+ * its own schema type (e.g., orchestrator Database vs Platform Database).
12
+ */
13
+ export declare function createDb<T>(pool: pg.Pool): Kysely<T>;
14
+ //# sourceMappingURL=db.d.ts.map
package/dist/db.js ADDED
@@ -0,0 +1,23 @@
1
+ import "./chunk-gOLHoazu.js";
2
+ import pg from "pg";
3
+ import { Kysely, PostgresDialect } from "kysely";
4
+ //#region src/db.ts
5
+ /**
6
+ * Create PostgreSQL connection pool.
7
+ */
8
+ function createPool(databaseUrl) {
9
+ return new pg.Pool({ connectionString: databaseUrl });
10
+ }
11
+ /**
12
+ * Create Kysely database instance (PostgreSQL only).
13
+ *
14
+ * Generic over the database type so each consumer can provide
15
+ * its own schema type (e.g., orchestrator Database vs Platform Database).
16
+ */
17
+ function createDb(pool) {
18
+ return new Kysely({ dialect: new PostgresDialect({ pool }) });
19
+ }
20
+ //#endregion
21
+ export { createDb, createPool };
22
+
23
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Source of truth for the env-var allowlist.
3
+ *
4
+ * KiCI's naming convention:
5
+ *
6
+ * > Every env var read by KiCI's own code MUST start with `KICI_`,
7
+ * > except for a fixed allowlist of OS / SDK names that KiCI consumes
8
+ * > but does not own (PATH, AWS_*, REDIS_*, OTEL_*, etc.).
9
+ *
10
+ * Two artefacts read this file:
11
+ *
12
+ * 1. `packages/shared/src/env/env-rule-allowlist.test.ts` — a runtime
13
+ * backstop that walks the source tree and matches every
14
+ * `process[.]env[.]NAME` access against `IS_ALLOWED_ENV_NAME(NAME)`.
15
+ * Catches any read that the inline ESLint rule missed. The backstop
16
+ * strips template-literal contents before scanning, so workflow
17
+ * source code embedded in fixture-emitting helpers does NOT
18
+ * contribute to the read set.
19
+ * 2. `eslint.config.js` — an inline `no-restricted-syntax` rule that
20
+ * mirrors the same regex at lint time. The eslint config builds the
21
+ * regex INLINE (it can't import from a TS file at lint init), so any
22
+ * change here MUST also be reflected there. The backstop test fails
23
+ * loudly if the two drift.
24
+ *
25
+ * The phased rename that introduced this rule (P0–P8) is complete: the
26
+ * `MIGRATING_ENV_VARS` ratchet is gone and `KICI_*` is the only accepted
27
+ * project-internal namespace. New env vars MUST use `KICI_*` — see
28
+ * `.claude/rules/env-vars.md` for the convention and how to add one
29
+ * (always `KICI_*`, register in the relevant package's `defineEnv`
30
+ * envMap, regenerate `docs/operator/env-reference.md`, done).
31
+ */
32
+ /**
33
+ * Anchored regex matching the OS / SDK / external-system env var names
34
+ * that KiCI is the *consumer* of. KiCI does not own these names, so it
35
+ * can't prefix them. Plus the `KICI_*` prefix itself for everything we
36
+ * do own.
37
+ *
38
+ * Categories:
39
+ * - `KICI_*` — every project-internal env var.
40
+ * - OS basics — NODE_ENV, HOME, PATH, TZ, LANG, TMPDIR,
41
+ * USER, USERNAME, SHELL, COMSPEC, PWD, OLDPWD,
42
+ * HOSTNAME.
43
+ * - Terminal / display — COLUMNS, LINES, TERM, COLORTERM, DISPLAY,
44
+ * WAYLAND_DISPLAY, LOCALAPPDATA.
45
+ * - XDG basedir spec — XDG_*.
46
+ * - npm / SSH wrappers — INIT_CWD, npm_*, SSH_*.
47
+ * - CI provider hints — CI, GITHUB_ACTIONS, GITHUB_ENV, GITHUB_OUTPUT,
48
+ * GITHUB_PATH, GITHUB_STEP_SUMMARY, GITLAB_CI.
49
+ * - Cloud / SDK names — AWS_*, REDIS_*, OTEL_*, STRIPE_*, DOCKER_*,
50
+ * CONTAINER_HOST.
51
+ * - Postgres libpq — PGHOST, PGPORT, PGUSER, PGPASSWORD,
52
+ * PGDATABASE, PGSERVICEFILE, PGSSLMODE.
53
+ * - Forgejo dev server — FORGEJO_URL, FORGEJO_CONTAINER (read by the
54
+ * staging Forgejo bootstrap script; conventional
55
+ * names owned by Forgejo, not KiCI).
56
+ * - Zitadel SDK config — ZITADEL_* (canonical names for the staging
57
+ * Zitadel client config: issuer, project ID,
58
+ * CLI client ID, test/admin user SUBs, the
59
+ * staging Terraform PAT, and the management
60
+ * API base URL). NOT aliases of `KICI_OIDC_*` —
61
+ * these are the names Zitadel itself uses.
62
+ * - Vite client config — VITE_* (Vite reserves this prefix for env
63
+ * vars exposed to client bundles; `VITE_BASE`
64
+ * and `VITE_DOCS_BASE_URL` flow through this
65
+ * channel).
66
+ * - Playwright runtime — PLAYWRIGHT (test-runner-set switch the
67
+ * dashboard's vite.config consults to disable
68
+ * the dev proxy), HEADED (Playwright convention
69
+ * for `--headed` runs).
70
+ */
71
+ export declare const OS_SDK_ALLOWLIST_REGEX: RegExp;
72
+ /**
73
+ * Returns true when `name` is allowed under the KiCI env-var convention:
74
+ * it matches the OS/SDK allowlist regex (which includes the `KICI_*`
75
+ * prefix). The phased migration is complete; there is no longer a
76
+ * separate "migrating" set.
77
+ */
78
+ export declare function IS_ALLOWED_ENV_NAME(name: string): boolean;
79
+ //# sourceMappingURL=allowlist.d.ts.map
@@ -0,0 +1,86 @@
1
+ import "../chunk-gOLHoazu.js";
2
+ //#region src/env/allowlist.ts
3
+ /**
4
+ * Source of truth for the env-var allowlist.
5
+ *
6
+ * KiCI's naming convention:
7
+ *
8
+ * > Every env var read by KiCI's own code MUST start with `KICI_`,
9
+ * > except for a fixed allowlist of OS / SDK names that KiCI consumes
10
+ * > but does not own (PATH, AWS_*, REDIS_*, OTEL_*, etc.).
11
+ *
12
+ * Two artefacts read this file:
13
+ *
14
+ * 1. `packages/shared/src/env/env-rule-allowlist.test.ts` — a runtime
15
+ * backstop that walks the source tree and matches every
16
+ * `process[.]env[.]NAME` access against `IS_ALLOWED_ENV_NAME(NAME)`.
17
+ * Catches any read that the inline ESLint rule missed. The backstop
18
+ * strips template-literal contents before scanning, so workflow
19
+ * source code embedded in fixture-emitting helpers does NOT
20
+ * contribute to the read set.
21
+ * 2. `eslint.config.js` — an inline `no-restricted-syntax` rule that
22
+ * mirrors the same regex at lint time. The eslint config builds the
23
+ * regex INLINE (it can't import from a TS file at lint init), so any
24
+ * change here MUST also be reflected there. The backstop test fails
25
+ * loudly if the two drift.
26
+ *
27
+ * The phased rename that introduced this rule (P0–P8) is complete: the
28
+ * `MIGRATING_ENV_VARS` ratchet is gone and `KICI_*` is the only accepted
29
+ * project-internal namespace. New env vars MUST use `KICI_*` — see
30
+ * `.claude/rules/env-vars.md` for the convention and how to add one
31
+ * (always `KICI_*`, register in the relevant package's `defineEnv`
32
+ * envMap, regenerate `docs/operator/env-reference.md`, done).
33
+ */
34
+ /**
35
+ * Anchored regex matching the OS / SDK / external-system env var names
36
+ * that KiCI is the *consumer* of. KiCI does not own these names, so it
37
+ * can't prefix them. Plus the `KICI_*` prefix itself for everything we
38
+ * do own.
39
+ *
40
+ * Categories:
41
+ * - `KICI_*` — every project-internal env var.
42
+ * - OS basics — NODE_ENV, HOME, PATH, TZ, LANG, TMPDIR,
43
+ * USER, USERNAME, SHELL, COMSPEC, PWD, OLDPWD,
44
+ * HOSTNAME.
45
+ * - Terminal / display — COLUMNS, LINES, TERM, COLORTERM, DISPLAY,
46
+ * WAYLAND_DISPLAY, LOCALAPPDATA.
47
+ * - XDG basedir spec — XDG_*.
48
+ * - npm / SSH wrappers — INIT_CWD, npm_*, SSH_*.
49
+ * - CI provider hints — CI, GITHUB_ACTIONS, GITHUB_ENV, GITHUB_OUTPUT,
50
+ * GITHUB_PATH, GITHUB_STEP_SUMMARY, GITLAB_CI.
51
+ * - Cloud / SDK names — AWS_*, REDIS_*, OTEL_*, STRIPE_*, DOCKER_*,
52
+ * CONTAINER_HOST.
53
+ * - Postgres libpq — PGHOST, PGPORT, PGUSER, PGPASSWORD,
54
+ * PGDATABASE, PGSERVICEFILE, PGSSLMODE.
55
+ * - Forgejo dev server — FORGEJO_URL, FORGEJO_CONTAINER (read by the
56
+ * staging Forgejo bootstrap script; conventional
57
+ * names owned by Forgejo, not KiCI).
58
+ * - Zitadel SDK config — ZITADEL_* (canonical names for the staging
59
+ * Zitadel client config: issuer, project ID,
60
+ * CLI client ID, test/admin user SUBs, the
61
+ * staging Terraform PAT, and the management
62
+ * API base URL). NOT aliases of `KICI_OIDC_*` —
63
+ * these are the names Zitadel itself uses.
64
+ * - Vite client config — VITE_* (Vite reserves this prefix for env
65
+ * vars exposed to client bundles; `VITE_BASE`
66
+ * and `VITE_DOCS_BASE_URL` flow through this
67
+ * channel).
68
+ * - Playwright runtime — PLAYWRIGHT (test-runner-set switch the
69
+ * dashboard's vite.config consults to disable
70
+ * the dev proxy), HEADED (Playwright convention
71
+ * for `--headed` runs).
72
+ */
73
+ const OS_SDK_ALLOWLIST_REGEX = /^(KICI_.*|NODE_ENV|HOME|PATH|TZ|LANG|TMPDIR|USER|USERNAME|SHELL|COMSPEC|PWD|OLDPWD|HOSTNAME|COLUMNS|LINES|TERM|COLORTERM|DISPLAY|WAYLAND_DISPLAY|LOCALAPPDATA|XDG_CACHE_HOME|XDG_CONFIG_HOME|XDG_DATA_HOME|XDG_RUNTIME_DIR|XDG_STATE_HOME|INIT_CWD|npm_.*|SSH_.*|CI|GITHUB_ACTIONS|GITHUB_ENV|GITHUB_OUTPUT|GITHUB_PATH|GITHUB_STEP_SUMMARY|GITLAB_CI|AWS_.*|REDIS_.*|OTEL_.*|STRIPE_.*|DOCKER_.*|CONTAINER_HOST|container|PGHOST|PGPORT|PGUSER|PGPASSWORD|PGDATABASE|PGSERVICEFILE|PGSSLMODE|FORGEJO_URL|FORGEJO_CONTAINER|ZITADEL_.*|VITE_.*|PLAYWRIGHT|HEADED)$/;
74
+ /**
75
+ * Returns true when `name` is allowed under the KiCI env-var convention:
76
+ * it matches the OS/SDK allowlist regex (which includes the `KICI_*`
77
+ * prefix). The phased migration is complete; there is no longer a
78
+ * separate "migrating" set.
79
+ */
80
+ function IS_ALLOWED_ENV_NAME(name) {
81
+ return OS_SDK_ALLOWLIST_REGEX.test(name);
82
+ }
83
+ //#endregion
84
+ export { IS_ALLOWED_ENV_NAME, OS_SDK_ALLOWLIST_REGEX };
85
+
86
+ //# sourceMappingURL=allowlist.js.map
@@ -0,0 +1,172 @@
1
+ /**
2
+ * defineEnv — small helper that ties a Zod schema to its env-var mapping so
3
+ * services can:
4
+ * 1. parse process.env into a typed config (the existing pattern, just
5
+ * centralised),
6
+ * 2. reject unknown KICI_* env vars (typo catcher),
7
+ * 3. emit machine-readable field metadata for docs/operator/env-reference.md.
8
+ *
9
+ * The helper deliberately stays small. Each service still owns its schema
10
+ * (defaults, refinements, cross-field rules); we only standardise the boring
11
+ * env-name <-> field-name plumbing and the "did you mean ...?" UX.
12
+ *
13
+ * `envMap` accepts:
14
+ * - a string: the single env var that backs this top-level field, OR
15
+ * - a string[]: multiple aliases, first non-undefined wins (legacy
16
+ * pattern; the only historical user was the agent's
17
+ * KICI_JOB_HEARTBEAT_INTERVAL_MS alias, collapsed in P3 of the
18
+ * env-var standardization plan). OR
19
+ * - an object: nested map for fields that are themselves z.object(...)
20
+ * (orchestrator's `cluster: { instanceId: '...', ... }`). Nested objects
21
+ * can recurse arbitrarily, but in practice we only need one level.
22
+ *
23
+ * The schema may be a plain z.object(...) OR a `.superRefine`'d composition
24
+ * built on top of one (orchestrator does this for cross-field rules). We keep
25
+ * the input type as `z.ZodType` so both shapes are accepted; we reach into
26
+ * `.shape` only when the caller asks for `describe()` output.
27
+ */
28
+ import { z } from 'zod';
29
+ /** Recursive env-var map. Leaf is the env var name (or array of aliases). */
30
+ export type EnvMapValue = string | string[] | EnvMap;
31
+ export interface EnvMap {
32
+ [field: string]: EnvMapValue;
33
+ }
34
+ /** Description of a single env var, suitable for docs generation. */
35
+ export interface EnvFieldSpec {
36
+ /** Env var name (the canonical one when there are aliases). */
37
+ envVar: string;
38
+ /** Aliases (other env vars that map to the same field). */
39
+ aliases: string[];
40
+ /** Dotted JS path inside the parsed config (e.g. `cluster.instanceId`). */
41
+ fieldPath: string;
42
+ /** Whether the field is required (no default, not optional). */
43
+ required: boolean;
44
+ /** Default value, if present, formatted as a string. */
45
+ defaultValue?: string;
46
+ /** Type label (e.g. `string`, `number`, `enum:a|b`, `boolean`). */
47
+ type: string;
48
+ /** Description from the schema's `.describe()`, or an explicit override. */
49
+ description?: string;
50
+ }
51
+ export interface DefineEnvOptions<TShape extends z.ZodRawShape> {
52
+ /** Service identifier (free-form, used for docs grouping + error messages). */
53
+ service: string;
54
+ /**
55
+ * The Zod object schema. Pass the underlying ZodObject before any
56
+ * `.superRefine(...)` chain so we can walk `.shape` for docs metadata. The
57
+ * returned `parse()` runs the full schema (with refinements) — pass that as
58
+ * the `parser`.
59
+ */
60
+ schema: z.ZodObject<TShape>;
61
+ /** Optional outer schema (e.g., `schema.superRefine(...)` for cross-field). */
62
+ parser?: z.ZodType;
63
+ /** Mapping from field name (or nested path) to env var(s). */
64
+ envMap: EnvMap;
65
+ /** Optional per-field description override (keyed by dotted path). */
66
+ descriptions?: Record<string, string>;
67
+ }
68
+ export interface DefineEnvResult<T> {
69
+ /** Parse `env` (defaults to `process.env`) into a typed config. */
70
+ parse(env?: NodeJS.ProcessEnv): T;
71
+ /** Machine-readable field specs for docs generation. */
72
+ describe(): EnvFieldSpec[];
73
+ /** Flat list of every env var the schema reads (for the unknown-var scanner). */
74
+ listKnownEnvVars(): string[];
75
+ /** Subset of `listKnownEnvVars()` starting with `KICI_`. */
76
+ listKnownKiciVars(): string[];
77
+ }
78
+ /**
79
+ * Build a `defineEnv` helper for a service.
80
+ *
81
+ * Example:
82
+ * const envDef = defineEnv({
83
+ * service: 'agent',
84
+ * schema: configSchema,
85
+ * envMap: {
86
+ * orchestratorUrl: 'KICI_ORCHESTRATOR_URL',
87
+ * cluster: { instanceId: 'KICI_CLUSTER_INSTANCE_ID' },
88
+ * },
89
+ * });
90
+ * const config = envDef.parse();
91
+ */
92
+ export declare function defineEnv<TShape extends z.ZodRawShape>(opts: DefineEnvOptions<TShape>): DefineEnvResult<z.infer<z.ZodObject<TShape>>>;
93
+ /**
94
+ * `KICI_*` env vars that are NOT part of any service's config schema but are
95
+ * legitimately set by packaging / tooling / dev shims. The scanner always
96
+ * allowlists these so the typo-catcher doesn't false-positive on them.
97
+ *
98
+ * - `KICI_CACHE`: set by the packaged CLI shim (packages/… .cmd and the
99
+ * POSIX equivalent emitted by scripts/package.mjs) to point at the cached
100
+ * Node.js binary. Inherited by the orchestrator/agent process on Windows
101
+ * (on POSIX it's a shell-local var that doesn't leak).
102
+ * - `KICI_DEV`: the dev-mode toggle itself — read by the scanner to flip
103
+ * to warn-only, so it must not trip the scanner.
104
+ *
105
+ * Keep this list small and well-justified. Every addition is a typo we can
106
+ * no longer catch, so only list things that are (a) actually set in the
107
+ * wild by our own tooling and (b) could never be a config typo.
108
+ */
109
+ export declare const RESERVED_NON_SCHEMA_KICI_VARS: readonly string[];
110
+ /**
111
+ * `KICI_*` prefixes that are entirely outside the service-config namespace —
112
+ * usually set by our own test / dev tooling and inherited into a child
113
+ * orchestrator/agent/platform process by mistake of inheritance rather than
114
+ * design. Any env var starting with one of these prefixes is treated as
115
+ * known, regardless of the specific suffix.
116
+ *
117
+ * - `KICI_E2E_`: the E2E framework's namespace (`KICI_E2E_PROVIDER`,
118
+ * `KICI_E2E_MODE`, future E2E toggles). Set by `e2e/vitest.*.config.ts`
119
+ * files. The native orchestrator spawn in `e2e/helpers/deploy.ts`
120
+ * inherits the test runner's process.env, so these leak in.
121
+ * - `KICI_AGENT_ENV_`: the agent-env forwarding namespace (see
122
+ * `@kici-dev/engine` → `KICI_AGENT_ENV_PREFIX`). Any env var set on the
123
+ * orchestrator process with this prefix is stripped and forwarded into
124
+ * the spawned agent (bare-metal, container, or Firecracker MMDS). The
125
+ * suffix is by design arbitrary and user-controlled, so it cannot be
126
+ * enumerated in the schema.
127
+ *
128
+ * As with RESERVED_NON_SCHEMA_KICI_VARS, keep this short — each prefix
129
+ * widens the set of names we can no longer catch as typos.
130
+ */
131
+ export declare const RESERVED_NON_SCHEMA_KICI_PREFIXES: readonly string[];
132
+ /**
133
+ * `KICI_*` suffix patterns that mark vars outside the service-config namespace.
134
+ * A var is treated as known when its name ends with any listed suffix
135
+ * (or the suffix appears as a boundary segment — matching
136
+ * `${suffix}$|${suffix}_`, same rule as the agent's probe collector).
137
+ *
138
+ * - `_ENV_PROBE`: diagnostic probe vars (see `packages/agent/src/server.ts` —
139
+ * the agent collects every `KICI_*_ENV_PROBE` var and logs its value in
140
+ * "Agent startup env probes (diagnostic)"). The E2E firecracker-pipeline
141
+ * test uses `KICI_AGENT_ENV_KICI_FC_ENV_PROBE=fc-probe-value` which flows
142
+ * through the scaler's env-forwarding path and lands in the agent's
143
+ * process.env as `KICI_FC_ENV_PROBE`. Without this suffix allowlist the
144
+ * validator rejects it as an unknown KICI_* var and the agent refuses to
145
+ * start — defeating the test's entire purpose.
146
+ *
147
+ * As with RESERVED_NON_SCHEMA_KICI_VARS / _PREFIXES, keep this short. Every
148
+ * suffix widens the set of names we can no longer catch as typos.
149
+ */
150
+ export declare const RESERVED_NON_SCHEMA_KICI_SUFFIXES: readonly string[];
151
+ export interface ValidateUnknownKiciVarsOptions {
152
+ /** Extra env-var names to treat as known (not all consumers can be migrated in one go). */
153
+ extraKnown?: string[];
154
+ /**
155
+ * When `true` (default in dev mode), unknown KICI_* vars only log a warning
156
+ * via `onWarn`. When `false`, throw — the production behaviour.
157
+ */
158
+ warnOnly?: boolean;
159
+ /** Logger callback for warn-mode (defaults to `console.warn`). */
160
+ onWarn?: (msg: string) => void;
161
+ }
162
+ /**
163
+ * Inspect `env` for KICI_* keys that are not in `known`. Throws (or warns,
164
+ * see options) with a single combined message listing every unknown var and
165
+ * its closest legitimate match (when the Levenshtein distance is small).
166
+ *
167
+ * Production mode (the default): throws — staging deploys should never reach
168
+ * runtime with a typo'd KICI_* variable. Set `KICI_DEV=true` (or pass
169
+ * `warnOnly: true`) to downgrade to a warning during local development.
170
+ */
171
+ export declare function validateUnknownKiciVars(known: string[], options?: ValidateUnknownKiciVarsOptions, env?: NodeJS.ProcessEnv): void;
172
+ //# sourceMappingURL=define-env.d.ts.map
@@ -0,0 +1,295 @@
1
+ import "../chunk-gOLHoazu.js";
2
+ //#region src/env/define-env.ts
3
+ /** Read an env value following the alias precedence (first non-undefined wins). */
4
+ function readEnv(envMap, env) {
5
+ if (typeof envMap === "string") return env[envMap];
6
+ if (Array.isArray(envMap)) {
7
+ for (const name of envMap) {
8
+ const v = env[name];
9
+ if (v !== void 0) return v;
10
+ }
11
+ return;
12
+ }
13
+ const result = {};
14
+ for (const [k, v] of Object.entries(envMap)) result[k] = readEnv(v, env);
15
+ return result;
16
+ }
17
+ /** Walk an EnvMap and yield every leaf env var name. */
18
+ function* walkEnvNames(envMap) {
19
+ for (const value of Object.values(envMap)) if (typeof value === "string") yield value;
20
+ else if (Array.isArray(value)) for (const name of value) yield name;
21
+ else yield* walkEnvNames(value);
22
+ }
23
+ /** Best-effort type label for docs. Walks through default / optional / pipe / transform wrappers. */
24
+ function describeType(field) {
25
+ let t = field;
26
+ for (let i = 0; i < 8; i++) {
27
+ const def = t.def;
28
+ if (!def) return "unknown";
29
+ switch (def.type) {
30
+ case "string": return "string";
31
+ case "number": return "number";
32
+ case "boolean": return "boolean";
33
+ case "enum": return `enum:${(def.entries ? Object.keys(def.entries) : def.values ?? []).join("|")}`;
34
+ case "object": return "object";
35
+ case "optional":
36
+ case "default":
37
+ case "nullable":
38
+ if (def.innerType) {
39
+ t = def.innerType;
40
+ continue;
41
+ }
42
+ return def.type ?? "unknown";
43
+ case "pipe":
44
+ if (def.in) {
45
+ t = def.in;
46
+ continue;
47
+ }
48
+ return "string";
49
+ case "transform": return "string";
50
+ case "union": return "union";
51
+ default: return def.type ?? "unknown";
52
+ }
53
+ }
54
+ return "unknown";
55
+ }
56
+ /** Best-effort default value extraction for docs. Recurses into pipe/transform wrappers. */
57
+ function extractDefault(field) {
58
+ let t = field;
59
+ for (let i = 0; i < 6; i++) {
60
+ const def = t.def;
61
+ if (!def) return void 0;
62
+ if (def.type === "default" && def.defaultValue !== void 0) {
63
+ const v = typeof def.defaultValue === "function" ? "<computed>" : def.defaultValue;
64
+ if (v === "" || v === void 0) return void 0;
65
+ return JSON.stringify(v);
66
+ }
67
+ if (def.in) {
68
+ t = def.in;
69
+ continue;
70
+ }
71
+ if (def.innerType) {
72
+ t = def.innerType;
73
+ continue;
74
+ }
75
+ return;
76
+ }
77
+ }
78
+ function isOptional(field) {
79
+ let t = field;
80
+ for (let i = 0; i < 6; i++) {
81
+ const def = t.def;
82
+ if (!def) return false;
83
+ if (def.type === "optional" || def.type === "default") return true;
84
+ if (def.in) {
85
+ t = def.in;
86
+ continue;
87
+ }
88
+ if (def.innerType) {
89
+ t = def.innerType;
90
+ continue;
91
+ }
92
+ return false;
93
+ }
94
+ return false;
95
+ }
96
+ function describeFieldRecursive(shape, envMap, fieldPath, descriptions, out) {
97
+ for (const [name, field] of Object.entries(shape)) {
98
+ const path = fieldPath ? `${fieldPath}.${name}` : name;
99
+ const mapping = envMap[name];
100
+ if (mapping === void 0) continue;
101
+ if (typeof mapping === "string" || Array.isArray(mapping)) {
102
+ const aliases = Array.isArray(mapping) ? mapping : [mapping];
103
+ const explicitDesc = descriptions?.[path];
104
+ const zodDesc = field.description;
105
+ const def2 = field.def;
106
+ out.push({
107
+ envVar: aliases[0],
108
+ aliases: aliases.slice(1),
109
+ fieldPath: path,
110
+ required: !isOptional(field) && extractDefault(field) === void 0,
111
+ defaultValue: extractDefault(field),
112
+ type: describeType(field),
113
+ description: explicitDesc ?? zodDesc ?? def2?.description
114
+ });
115
+ } else {
116
+ const nested = field;
117
+ const innerShape = nested.shape ?? nested.def?.shape;
118
+ if (innerShape) describeFieldRecursive(innerShape, mapping, path, descriptions, out);
119
+ }
120
+ }
121
+ }
122
+ /**
123
+ * Build a `defineEnv` helper for a service.
124
+ *
125
+ * Example:
126
+ * const envDef = defineEnv({
127
+ * service: 'agent',
128
+ * schema: configSchema,
129
+ * envMap: {
130
+ * orchestratorUrl: 'KICI_ORCHESTRATOR_URL',
131
+ * cluster: { instanceId: 'KICI_CLUSTER_INSTANCE_ID' },
132
+ * },
133
+ * });
134
+ * const config = envDef.parse();
135
+ */
136
+ function defineEnv(opts) {
137
+ const parserSchema = opts.parser ?? opts.schema;
138
+ function parse(env = process.env) {
139
+ const raw = readEnv(opts.envMap, env);
140
+ const result = parserSchema.safeParse(raw);
141
+ if (!result.success) {
142
+ const errors = result.error.issues.map((err) => ` - ${err.path.join(".")}: ${err.message}`).join("\n");
143
+ throw new Error(`Configuration validation failed:\n${errors}`);
144
+ }
145
+ return result.data;
146
+ }
147
+ function describe() {
148
+ const out = [];
149
+ describeFieldRecursive(opts.schema.shape, opts.envMap, "", opts.descriptions, out);
150
+ return out.sort((a, b) => a.envVar.localeCompare(b.envVar));
151
+ }
152
+ function listKnownEnvVars() {
153
+ return [...new Set(walkEnvNames(opts.envMap))].sort();
154
+ }
155
+ function listKnownKiciVars() {
156
+ return listKnownEnvVars().filter((n) => n.startsWith("KICI_"));
157
+ }
158
+ return {
159
+ parse,
160
+ describe,
161
+ listKnownEnvVars,
162
+ listKnownKiciVars
163
+ };
164
+ }
165
+ /**
166
+ * Levenshtein distance between two strings. Small + dependency-free; we only
167
+ * use it to suggest alternates for unknown KICI_* env vars at boot. O(n*m) is
168
+ * fine because both strings are short env var names.
169
+ */
170
+ function levenshtein(a, b) {
171
+ if (a === b) return 0;
172
+ if (!a.length) return b.length;
173
+ if (!b.length) return a.length;
174
+ const prev = new Array(b.length + 1);
175
+ const curr = new Array(b.length + 1);
176
+ for (let j = 0; j <= b.length; j++) prev[j] = j;
177
+ for (let i = 1; i <= a.length; i++) {
178
+ curr[0] = i;
179
+ for (let j = 1; j <= b.length; j++) {
180
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
181
+ curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
182
+ }
183
+ for (let j = 0; j <= b.length; j++) prev[j] = curr[j];
184
+ }
185
+ return prev[b.length];
186
+ }
187
+ function suggestClosest(name, candidates) {
188
+ let best;
189
+ for (const c of candidates) {
190
+ const d = levenshtein(name, c);
191
+ if (best === void 0 || d < best.dist) best = {
192
+ name: c,
193
+ dist: d
194
+ };
195
+ }
196
+ if (best && best.dist <= Math.max(2, Math.floor(name.length / 4))) return best.name;
197
+ }
198
+ /**
199
+ * `KICI_*` env vars that are NOT part of any service's config schema but are
200
+ * legitimately set by packaging / tooling / dev shims. The scanner always
201
+ * allowlists these so the typo-catcher doesn't false-positive on them.
202
+ *
203
+ * - `KICI_CACHE`: set by the packaged CLI shim (packages/… .cmd and the
204
+ * POSIX equivalent emitted by scripts/package.mjs) to point at the cached
205
+ * Node.js binary. Inherited by the orchestrator/agent process on Windows
206
+ * (on POSIX it's a shell-local var that doesn't leak).
207
+ * - `KICI_DEV`: the dev-mode toggle itself — read by the scanner to flip
208
+ * to warn-only, so it must not trip the scanner.
209
+ *
210
+ * Keep this list small and well-justified. Every addition is a typo we can
211
+ * no longer catch, so only list things that are (a) actually set in the
212
+ * wild by our own tooling and (b) could never be a config typo.
213
+ */
214
+ const RESERVED_NON_SCHEMA_KICI_VARS = ["KICI_CACHE", "KICI_DEV"];
215
+ /**
216
+ * `KICI_*` prefixes that are entirely outside the service-config namespace —
217
+ * usually set by our own test / dev tooling and inherited into a child
218
+ * orchestrator/agent/platform process by mistake of inheritance rather than
219
+ * design. Any env var starting with one of these prefixes is treated as
220
+ * known, regardless of the specific suffix.
221
+ *
222
+ * - `KICI_E2E_`: the E2E framework's namespace (`KICI_E2E_PROVIDER`,
223
+ * `KICI_E2E_MODE`, future E2E toggles). Set by `e2e/vitest.*.config.ts`
224
+ * files. The native orchestrator spawn in `e2e/helpers/deploy.ts`
225
+ * inherits the test runner's process.env, so these leak in.
226
+ * - `KICI_AGENT_ENV_`: the agent-env forwarding namespace (see
227
+ * `@kici-dev/engine` → `KICI_AGENT_ENV_PREFIX`). Any env var set on the
228
+ * orchestrator process with this prefix is stripped and forwarded into
229
+ * the spawned agent (bare-metal, container, or Firecracker MMDS). The
230
+ * suffix is by design arbitrary and user-controlled, so it cannot be
231
+ * enumerated in the schema.
232
+ *
233
+ * As with RESERVED_NON_SCHEMA_KICI_VARS, keep this short — each prefix
234
+ * widens the set of names we can no longer catch as typos.
235
+ */
236
+ const RESERVED_NON_SCHEMA_KICI_PREFIXES = ["KICI_E2E_", "KICI_AGENT_ENV_"];
237
+ /**
238
+ * `KICI_*` suffix patterns that mark vars outside the service-config namespace.
239
+ * A var is treated as known when its name ends with any listed suffix
240
+ * (or the suffix appears as a boundary segment — matching
241
+ * `${suffix}$|${suffix}_`, same rule as the agent's probe collector).
242
+ *
243
+ * - `_ENV_PROBE`: diagnostic probe vars (see `packages/agent/src/server.ts` —
244
+ * the agent collects every `KICI_*_ENV_PROBE` var and logs its value in
245
+ * "Agent startup env probes (diagnostic)"). The E2E firecracker-pipeline
246
+ * test uses `KICI_AGENT_ENV_KICI_FC_ENV_PROBE=fc-probe-value` which flows
247
+ * through the scaler's env-forwarding path and lands in the agent's
248
+ * process.env as `KICI_FC_ENV_PROBE`. Without this suffix allowlist the
249
+ * validator rejects it as an unknown KICI_* var and the agent refuses to
250
+ * start — defeating the test's entire purpose.
251
+ *
252
+ * As with RESERVED_NON_SCHEMA_KICI_VARS / _PREFIXES, keep this short. Every
253
+ * suffix widens the set of names we can no longer catch as typos.
254
+ */
255
+ const RESERVED_NON_SCHEMA_KICI_SUFFIXES = ["_ENV_PROBE"];
256
+ /**
257
+ * Inspect `env` for KICI_* keys that are not in `known`. Throws (or warns,
258
+ * see options) with a single combined message listing every unknown var and
259
+ * its closest legitimate match (when the Levenshtein distance is small).
260
+ *
261
+ * Production mode (the default): throws — staging deploys should never reach
262
+ * runtime with a typo'd KICI_* variable. Set `KICI_DEV=true` (or pass
263
+ * `warnOnly: true`) to downgrade to a warning during local development.
264
+ */
265
+ function validateUnknownKiciVars(known, options = {}, env = process.env) {
266
+ const knownSet = new Set([
267
+ ...known,
268
+ ...options.extraKnown ?? [],
269
+ ...RESERVED_NON_SCHEMA_KICI_VARS
270
+ ]);
271
+ const unknown = [];
272
+ for (const key of Object.keys(env)) {
273
+ if (!key.startsWith("KICI_")) continue;
274
+ if (knownSet.has(key)) continue;
275
+ if (RESERVED_NON_SCHEMA_KICI_PREFIXES.some((p) => key.startsWith(p))) continue;
276
+ if (RESERVED_NON_SCHEMA_KICI_SUFFIXES.some((s) => key.endsWith(s) || key.includes(`${s}_`))) continue;
277
+ unknown.push({
278
+ name: key,
279
+ suggestion: suggestClosest(key, [...knownSet])
280
+ });
281
+ }
282
+ if (unknown.length === 0) return;
283
+ const header = `Unknown KICI_* env var(s) detected — refusing to start.
284
+ Set KICI_DEV=true to downgrade this check to a warning.
285
+ Unknown vars:\n${unknown.map(({ name, suggestion }) => suggestion ? ` - ${name} (did you mean ${suggestion}?)` : ` - ${name} (no close match in the schema)`).join("\n")}`;
286
+ if (options.warnOnly ?? env.KICI_DEV === "true") {
287
+ (options.onWarn ?? console.warn)(header);
288
+ return;
289
+ }
290
+ throw new Error(header);
291
+ }
292
+ //#endregion
293
+ export { RESERVED_NON_SCHEMA_KICI_PREFIXES, RESERVED_NON_SCHEMA_KICI_SUFFIXES, RESERVED_NON_SCHEMA_KICI_VARS, defineEnv, validateUnknownKiciVars };
294
+
295
+ //# sourceMappingURL=define-env.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=define-env.test.d.ts.map