@kici-dev/shared 0.1.26 → 0.2.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.
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ /** Supported fresh-box agent target platforms (glibc-Linux bootstrap set). */
3
+ export declare const AgentPlatform: z.ZodEnum<{
4
+ "linux-arm64": "linux-arm64";
5
+ "linux-x64": "linux-x64";
6
+ }>;
7
+ export type AgentPlatform = z.infer<typeof AgentPlatform>;
8
+ /**
9
+ * How a fresh-box agent payload is delivered to the target during bring-up.
10
+ * `s3-direct`: the box pulls the payload from the orchestrator cache bucket via
11
+ * a presigned URL (no 50 MB through the ops agent). `ssh-push`: the ops agent
12
+ * fetches the payload and streams it to the box over a binary-safe scp (the
13
+ * fallback for a box that cannot reach object storage).
14
+ */
15
+ export declare const AgentDeliveryMode: z.ZodEnum<{
16
+ "s3-direct": "s3-direct";
17
+ "ssh-push": "ssh-push";
18
+ }>;
19
+ export type AgentDeliveryMode = z.infer<typeof AgentDeliveryMode>;
20
+ export interface AgentPlatformParts {
21
+ /** nodejs.org os token. */
22
+ nodeOs: 'linux';
23
+ /** nodejs.org arch token. */
24
+ nodeArch: 'x64' | 'arm64';
25
+ /** npm `--os` token. */
26
+ npmOs: 'linux';
27
+ /** npm `--cpu` token. */
28
+ npmCpu: 'x64' | 'arm64';
29
+ }
30
+ /** Decompose an AgentPlatform into the os/arch tokens npm and nodejs.org expect. */
31
+ export declare function splitAgentPlatform(platform: AgentPlatform): AgentPlatformParts;
32
+ //# sourceMappingURL=agent-platform.d.ts.map
@@ -0,0 +1,27 @@
1
+ import "./rolldown-runtime-ClRpJifh.js";
2
+ import { z } from "zod";
3
+ //#region src/agent-platform.ts
4
+ /** Supported fresh-box agent target platforms (glibc-Linux bootstrap set). */
5
+ const AgentPlatform = z.enum(["linux-x64", "linux-arm64"]);
6
+ /**
7
+ * How a fresh-box agent payload is delivered to the target during bring-up.
8
+ * `s3-direct`: the box pulls the payload from the orchestrator cache bucket via
9
+ * a presigned URL (no 50 MB through the ops agent). `ssh-push`: the ops agent
10
+ * fetches the payload and streams it to the box over a binary-safe scp (the
11
+ * fallback for a box that cannot reach object storage).
12
+ */
13
+ const AgentDeliveryMode = z.enum(["ssh-push", "s3-direct"]);
14
+ /** Decompose an AgentPlatform into the os/arch tokens npm and nodejs.org expect. */
15
+ function splitAgentPlatform(platform) {
16
+ const arch = platform === "linux-x64" ? "x64" : "arm64";
17
+ return {
18
+ nodeOs: "linux",
19
+ nodeArch: arch,
20
+ npmOs: "linux",
21
+ npmCpu: arch
22
+ };
23
+ }
24
+ //#endregion
25
+ export { AgentDeliveryMode, AgentPlatform, splitAgentPlatform };
26
+
27
+ //# sourceMappingURL=agent-platform.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=agent-platform.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ export * from '@kici-dev/core/ci-env';
2
+ //# sourceMappingURL=ci-env.d.ts.map
package/dist/ci-env.js ADDED
@@ -0,0 +1,3 @@
1
+ import "./rolldown-runtime-ClRpJifh.js";
2
+ export * from "@kici-dev/core/ci-env";
3
+ export {};
@@ -21,6 +21,6 @@ export declare function coldDaysToBucket(days: ColdRetention): string;
21
21
  */
22
22
  export declare function isLongerColdRetention(a: ColdRetention, b: ColdRetention): boolean;
23
23
  /** Stable, ordered set of bucket names emitted by `coldDaysToBucket`. */
24
- export declare const COLD_BUCKET_NAMES: readonly ["30d", "180d", "1y", "2y", "forever"];
24
+ export declare const COLD_BUCKET_NAMES: readonly ['30d', '180d', '1y', '2y', 'forever'];
25
25
  export type ColdBucketName = (typeof COLD_BUCKET_NAMES)[number];
26
26
  //# sourceMappingURL=bucket.d.ts.map
@@ -37,7 +37,12 @@ export interface ColdStoreFetchRangeArgs<TRow> {
37
37
  table: string;
38
38
  tenantId: string;
39
39
  fromTs: Date;
40
- toTs: Date;
40
+ /**
41
+ * Upper bound (exclusive). Omit to use `warmCutoff(table)` — the table's
42
+ * resolved warm/cold boundary. Pass an explicit value only when the caller
43
+ * genuinely owns the range (e.g. a user-supplied time filter).
44
+ */
45
+ toTs?: Date;
41
46
  decode?: (line: string) => TRow;
42
47
  }
43
48
  export interface ColdStoreReplayChunkArgs {
@@ -125,6 +130,23 @@ export interface PurgeableChunk {
125
130
  * flow.
126
131
  */
127
132
  export interface ColdStore {
133
+ /**
134
+ * The table's warm/cold boundary: `now − warmTtlDays`, resolved from the
135
+ * SAME config the archival writer uses — the registered adapter's effective
136
+ * config (built-in defaults merged with any
137
+ * `KICI_COLD_STORE_<TABLE>_WARM_TTL_DAYS` env override). Rows older than
138
+ * this may live in cold storage; rows newer are guaranteed PG-only.
139
+ *
140
+ * Readers MUST derive their cold-read upper bound from this rather than
141
+ * restating a literal. A reader-side value LARGER than the writer's
142
+ * produces an EARLIER cutoff, which silently hides archived rows: a
143
+ * manifest whose `min` timestamp is newer than the bound is skipped
144
+ * entirely, so a read 404s on data that exists in S3.
145
+ *
146
+ * A `warmTtlDays` of zero or less yields a cutoff at or after `now` — the
147
+ * widest window, which is the safe direction.
148
+ */
149
+ warmCutoff(table: string): Date;
128
150
  /** Stream archived rows that overlap [fromTs, toTs). */
129
151
  fetchRange<TRow>(args: ColdStoreFetchRangeArgs<TRow>): AsyncIterable<TRow>;
130
152
  hasRange(args: Omit<ColdStoreFetchRangeArgs<unknown>, 'decode'>): Promise<boolean>;
@@ -268,6 +290,7 @@ export declare abstract class BaseColdStore implements ColdStore {
268
290
  private putChunkData;
269
291
  private verifyChunkData;
270
292
  private putManifest;
293
+ warmCutoff(table: string): Date;
271
294
  fetchRange<TRow>(args: ColdStoreFetchRangeArgs<TRow>): AsyncIterable<TRow>;
272
295
  hasRange(args: Omit<ColdStoreFetchRangeArgs<unknown>, 'decode'>): Promise<boolean>;
273
296
  countRange(args: Omit<ColdStoreFetchRangeArgs<unknown>, 'decode'>): Promise<number>;
@@ -6,6 +6,7 @@ import { computeChunkId } from "./chunk-id.js";
6
6
  import { decodeChunk, encodeChunk } from "./chunk-encoder.js";
7
7
  import { parseManifest, serializeManifest } from "./manifest.js";
8
8
  import { coldStoreArchiveBytesTotal, coldStoreArchiveCyclesTotal, coldStoreArchiveDurationSeconds, coldStoreArchiveRowsTotal, coldStorePurgeBytesTotal, coldStorePurgeChunksTotal, coldStorePurgeDurationSeconds, coldStoreRehydrateBytesTotal, coldStoreRehydrateDurationSeconds, coldStoreRehydrateRequestsTotal, coldStoreReplayDurationSeconds, coldStoreReplayRowsTotal, coldStoreVerifyFailuresTotal } from "./metrics.js";
9
+ import { resolveTableConfig } from "./config.js";
9
10
  import { sha256 } from "@kici-dev/core";
10
11
  import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand } from "@aws-sdk/client-s3";
11
12
  //#region src/cold-store/cold-store.ts
@@ -195,7 +196,7 @@ var BaseColdStore = class {
195
196
  summary.skipped.disabled += 1;
196
197
  return;
197
198
  }
198
- const warmCutoff = /* @__PURE__ */ new Date(Date.now() - adapter.config.warmTtlDays * 864e5);
199
+ const warmCutoff = this.warmCutoff(adapter.table);
199
200
  let rowsThisCycle = 0;
200
201
  for await (const { tenantId, partitionDate } of adapter.listEligiblePartitions({ warmCutoff })) {
201
202
  if (rowsThisCycle >= adapter.config.maxRowsPerCycle) {
@@ -561,17 +562,22 @@ var BaseColdStore = class {
561
562
  ContentType: "application/json"
562
563
  }));
563
564
  }
565
+ warmCutoff(table) {
566
+ const cfg = this.adapters.get(table)?.config ?? resolveTableConfig(this.config.tables[table]);
567
+ return /* @__PURE__ */ new Date(Date.now() - cfg.warmTtlDays * 864e5);
568
+ }
564
569
  async *fetchRange(args) {
565
570
  if (!this.config.enabled) return;
566
571
  const adapter = this.adapters.get(args.table);
567
572
  const decodeLine = args.decode ?? (adapter ? (line) => adapter.decodeRow(line) : (line) => JSON.parse(line));
568
573
  const rowTimestamp = adapter ? (row) => this.toDate(adapter.rowTimestamp(row)) : null;
574
+ const toTs = args.toTs ?? this.warmCutoff(args.table);
569
575
  const manifests = await this.listRelevantManifests({
570
576
  db: args.db,
571
577
  table: args.table,
572
578
  tenantId: args.tenantId,
573
579
  fromTs: args.fromTs,
574
- toTs: args.toTs
580
+ toTs
575
581
  });
576
582
  const label = {
577
583
  db: args.db,
@@ -597,7 +603,7 @@ var BaseColdStore = class {
597
603
  })) {
598
604
  if (rowTimestamp) {
599
605
  const ts = rowTimestamp(row);
600
- if (ts < args.fromTs || ts >= args.toTs) continue;
606
+ if (ts < args.fromTs || ts >= toTs) continue;
601
607
  }
602
608
  yield row;
603
609
  }
@@ -605,11 +611,17 @@ var BaseColdStore = class {
605
611
  }
606
612
  async hasRange(args) {
607
613
  if (!this.config.enabled) return false;
608
- return (await this.listRelevantManifests(args)).length > 0;
614
+ return (await this.listRelevantManifests({
615
+ ...args,
616
+ toTs: args.toTs ?? this.warmCutoff(args.table)
617
+ })).length > 0;
609
618
  }
610
619
  async countRange(args) {
611
620
  if (!this.config.enabled) return 0;
612
- const manifests = await this.listRelevantManifests(args);
621
+ const manifests = await this.listRelevantManifests({
622
+ ...args,
623
+ toTs: args.toTs ?? this.warmCutoff(args.table)
624
+ });
613
625
  let total = 0;
614
626
  for (const m of manifests) total += m.rowCount;
615
627
  return total;
@@ -5,7 +5,7 @@ import { computeChunkId } from "./chunk-id.js";
5
5
  import { decodeChunk, encodeChunk } from "./chunk-encoder.js";
6
6
  import { parseManifest, serializeManifest } from "./manifest.js";
7
7
  import { coldStoreArchiveBytesTotal, coldStoreArchiveCyclesTotal, coldStoreArchiveDurationSeconds, coldStoreArchiveRowsTotal, coldStorePurgeBytesTotal, coldStorePurgeChunksTotal, coldStorePurgeDurationSeconds, coldStoreRehydrateBytesTotal, coldStoreRehydrateDurationSeconds, coldStoreRehydrateRequestsTotal, coldStoreReplayDurationSeconds, coldStoreReplayRowsTotal, coldStoreVerifyFailuresTotal } from "./metrics.js";
8
+ import { DEFAULT_TABLE_CONFIG, resolveTableConfig } from "./config.js";
8
9
  import { BaseColdStore } from "./cold-store.js";
9
10
  import { ChunkLru } from "./lru.js";
10
- import { DEFAULT_TABLE_CONFIG, resolveTableConfig } from "./config.js";
11
11
  export { BaseColdStore, COLD_BUCKET_NAMES, ChunkLru, DEFAULT_TABLE_CONFIG, chunkObjectKey, coldDaysToBucket, coldStoreArchiveBytesTotal, coldStoreArchiveCyclesTotal, coldStoreArchiveDurationSeconds, coldStoreArchiveRowsTotal, coldStorePurgeBytesTotal, coldStorePurgeChunksTotal, coldStorePurgeDurationSeconds, coldStoreRehydrateBytesTotal, coldStoreRehydrateDurationSeconds, coldStoreRehydrateRequestsTotal, coldStoreReplayDurationSeconds, coldStoreReplayRowsTotal, coldStoreVerifyFailuresTotal, computeChunkId, decodeChunk, encodeChunk, encodeKeySegment, isLongerColdRetention, parseManifest, resolveTableConfig, serializeManifest, tablePrefix, tenantDayBucketPrefix, tenantDayPrefix };
@@ -178,22 +178,22 @@ export declare function purgeScopedSecretsDirect(databaseUrl: string, orgId?: st
178
178
  deleted: number;
179
179
  }>;
180
180
  /**
181
- * Bulk-delete `environments` (and their FK-dependent rows) for an org, or for
182
- * every org when `orgId` is omitted. `environment_bindings` /
183
- * `environment_variables` / `environment_source_overrides` cascade
181
+ * Bulk-delete `contexts` (and their FK-dependent rows) for an org, or for
182
+ * every org when `orgId` is omitted. `context_bindings` /
183
+ * `context_variables` / `context_source_overrides` cascade
184
184
  * automatically (ON DELETE CASCADE). `held_runs` and `execution_runs` reference
185
- * `environments(id)` with ON DELETE SET NULL, so deleting environments alone
186
- * would leave orphaned `held_runs` rows carrying a null environment reference;
185
+ * `contexts(id)` with ON DELETE SET NULL, so deleting contexts alone
186
+ * would leave orphaned `held_runs` rows carrying a null context reference;
187
187
  * this helper deletes the org's `held_runs` too so a warm-start reset gets a
188
188
  * clean slate. Runs in a transaction so both deletes commit atomically. Used by
189
- * the E2E warm-start reset (so seeded environments don't leak between
190
- * categories) and exposed via `kici-admin environment purge`.
189
+ * the E2E warm-start reset (so seeded contexts don't leak between
190
+ * categories) and exposed via `kici-admin context purge`.
191
191
  */
192
- export declare function purgeEnvironmentsDirect(databaseUrl: string, orgId?: string): Promise<{
193
- environmentsDeleted: number;
192
+ export declare function purgeContextsDirect(databaseUrl: string, orgId?: string): Promise<{
193
+ contextsDeleted: number;
194
194
  heldRunsDeleted: number;
195
195
  }>;
196
- export interface SeedEnvironmentOpts {
196
+ export interface SeedContextOpts {
197
197
  orgId: string;
198
198
  name: string;
199
199
  type?: string;
@@ -205,52 +205,58 @@ export interface SeedEnvironmentOpts {
205
205
  minimumTrust?: string | null;
206
206
  globPattern?: string | null;
207
207
  }
208
- export interface SeedEnvironmentResult {
208
+ export interface SeedContextResult {
209
209
  envId: string;
210
210
  created: boolean;
211
211
  }
212
212
  /**
213
- * Upsert an environment row keyed by (org_id, name). Returns the env id and
213
+ * Upsert a context row keyed by (org_id, name). Returns the env id and
214
214
  * whether the row was newly inserted. `branchRestrictions` / `requiredReviewers`
215
215
  * are JSON-serialised server-side; pass them as plain arrays or objects.
216
- */
217
- export declare function seedEnvironmentDirect(databaseUrl: string, opts: SeedEnvironmentOpts): Promise<SeedEnvironmentResult>;
218
- export interface DeleteEnvironmentOpts {
216
+ *
217
+ * An omitted `holdExpirySeconds` is written as NULL rather than a literal
218
+ * window: the column carries no DDL default, so "never set" and "cleared" both
219
+ * land on NULL and resolve through the one `DEFAULT_HOLD_EXPIRY_SECONDS`
220
+ * fallback on read. Writing a literal here would give this path a second,
221
+ * longer default that no read-side code knows about.
222
+ */
223
+ export declare function seedContextDirect(databaseUrl: string, opts: SeedContextOpts): Promise<SeedContextResult>;
224
+ export interface DeleteContextOpts {
219
225
  orgId: string;
220
226
  name: string;
221
227
  }
222
228
  /**
223
- * Delete an environment keyed by (org_id, name). Returns whether a row was
224
- * removed. The `environment_bindings`, `environment_variables`, and
225
- * `environment_source_overrides` children all carry
226
- * `FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE CASCADE`,
227
- * so a single DELETE on `environments` cascades to those children. The
229
+ * Delete a context keyed by (org_id, name). Returns whether a row was
230
+ * removed. The `context_bindings`, `context_variables`, and
231
+ * `context_source_overrides` children all carry
232
+ * `FOREIGN KEY (context_id) REFERENCES contexts(id) ON DELETE CASCADE`,
233
+ * so a single DELETE on `contexts` cascades to those children. The
228
234
  * `held_runs` FK uses `ON DELETE SET NULL`, so terminal held-run history
229
- * survives the delete with a null environment reference. Pending held runs
230
- * still reference the environment, so this helper pre-checks their count and
235
+ * survives the delete with a null context reference. Pending held runs
236
+ * still reference the context, so this helper pre-checks their count and
231
237
  * throws before issuing the DELETE — approve or reject them first.
232
238
  */
233
- export declare function deleteEnvironmentDirect(databaseUrl: string, opts: DeleteEnvironmentOpts): Promise<{
239
+ export declare function deleteContextDirect(databaseUrl: string, opts: DeleteContextOpts): Promise<{
234
240
  deleted: boolean;
235
241
  }>;
236
- export interface SeedEnvironmentBindingOpts {
242
+ export interface SeedContextBindingOpts {
237
243
  orgId: string;
238
- envName: string;
244
+ contextName: string;
239
245
  scopePattern: string;
240
246
  /** Host selector; defaults to `'**'` (all hosts). */
241
247
  hostPattern?: string;
242
248
  }
243
249
  /**
244
- * Upsert an `environment_bindings` row connecting `envName` to `scopePattern`
245
- * (scoped to `hostPattern`, default `'**'`). Throws if the environment does
250
+ * Upsert an `context_bindings` row connecting `contextName` to `scopePattern`
251
+ * (scoped to `hostPattern`, default `'**'`). Throws if the context does
246
252
  * not exist.
247
253
  */
248
- export declare function seedEnvironmentBindingDirect(databaseUrl: string, opts: SeedEnvironmentBindingOpts): Promise<{
254
+ export declare function seedContextBindingDirect(databaseUrl: string, opts: SeedContextBindingOpts): Promise<{
249
255
  created: boolean;
250
256
  }>;
251
- export interface SetEnvironmentPolicyOpts {
257
+ export interface SetContextPolicyOpts {
252
258
  orgId: string;
253
- envName: string;
259
+ contextName: string;
254
260
  branchRestrictions?: unknown;
255
261
  requiredReviewers?: unknown;
256
262
  waitTimerSeconds?: number | null;
@@ -261,10 +267,10 @@ export interface SetEnvironmentPolicyOpts {
261
267
  }
262
268
  /**
263
269
  * UPDATE only the policy fields that were explicitly provided. Columns that
264
- * were NOT in `opts` are left untouched. Throws if the environment is missing.
270
+ * were NOT in `opts` are left untouched. Throws if the context is missing.
265
271
  */
266
- export declare function setEnvironmentPolicyDirect(databaseUrl: string, opts: SetEnvironmentPolicyOpts): Promise<void>;
267
- export interface EnvironmentRow {
272
+ export declare function setContextPolicyDirect(databaseUrl: string, opts: SetContextPolicyOpts): Promise<void>;
273
+ export interface ContextRow {
268
274
  id: string;
269
275
  org_id: string;
270
276
  name: string;
@@ -279,38 +285,38 @@ export interface EnvironmentRow {
279
285
  updated_at: string;
280
286
  }
281
287
  /**
282
- * SELECT * FROM environments WHERE org_id = $1, ordered by name.
288
+ * SELECT * FROM contexts WHERE org_id = $1, ordered by name.
283
289
  */
284
- export declare function listEnvironmentsDirect(databaseUrl: string, opts: {
290
+ export declare function listContextsDirect(databaseUrl: string, opts: {
285
291
  orgId: string;
286
292
  }): Promise<{
287
- environments: EnvironmentRow[];
293
+ contexts: ContextRow[];
288
294
  }>;
289
- export interface EnvironmentVariableRow {
295
+ export interface ContextVariableRow {
290
296
  key: string;
291
297
  value: string;
292
298
  locked: boolean;
293
299
  updated_at: string;
294
300
  }
295
- export interface EnvironmentBindingRow {
301
+ export interface ContextBindingRow {
296
302
  scope_pattern: string;
297
303
  host_pattern: string;
298
304
  created_at: string;
299
305
  }
300
- export interface ShowEnvironmentResult {
301
- environment: EnvironmentRow;
302
- variables: EnvironmentVariableRow[];
303
- bindings: EnvironmentBindingRow[];
306
+ export interface ShowContextResult {
307
+ context: ContextRow;
308
+ variables: ContextVariableRow[];
309
+ bindings: ContextBindingRow[];
304
310
  }
305
311
  /**
306
- * Fetch a single environment row joined with its variables and bindings.
307
- * Throws if the environment does not exist.
312
+ * Fetch a single context row joined with its variables and bindings.
313
+ * Throws if the context does not exist.
308
314
  */
309
- export declare function showEnvironmentDirect(databaseUrl: string, opts: {
315
+ export declare function showContextDirect(databaseUrl: string, opts: {
310
316
  orgId: string;
311
317
  name: string;
312
- }): Promise<ShowEnvironmentResult>;
313
- export interface CreateEnvironmentTemplateOpts {
318
+ }): Promise<ShowContextResult>;
319
+ export interface CreateContextTemplateOpts {
314
320
  orgId: string;
315
321
  templateName: string;
316
322
  type?: string;
@@ -322,27 +328,31 @@ export interface CreateEnvironmentTemplateOpts {
322
328
  variables?: Record<string, string>;
323
329
  }
324
330
  /**
325
- * Create (or update) an environment template + its seed variables in one
326
- * transaction. Templates are represented as environments with `type='template'`
331
+ * Create (or update) a context template + its seed variables in one
332
+ * transaction. Templates are represented as contexts with `type='template'`
327
333
  * by convention. Returns `{ envId, variablesSet }`.
334
+ *
335
+ * An omitted `holdExpirySeconds` is written as NULL rather than a literal
336
+ * window, for the same reason as `seedContextDirect`: the column has no DDL
337
+ * default and every read resolves NULL through `DEFAULT_HOLD_EXPIRY_SECONDS`.
328
338
  */
329
- export declare function createEnvironmentTemplateDirect(databaseUrl: string, opts: CreateEnvironmentTemplateOpts): Promise<{
339
+ export declare function createContextTemplateDirect(databaseUrl: string, opts: CreateContextTemplateOpts): Promise<{
330
340
  envId: string;
331
341
  created: boolean;
332
342
  variablesSet: number;
333
343
  }>;
334
- export interface SetEnvironmentSecretOpts {
344
+ export interface SetContextSecretOpts {
335
345
  orgId: string;
336
- environment: string;
346
+ context: string;
337
347
  key: string;
338
348
  encryptedValue: string;
339
349
  }
340
350
  /**
341
- * UPSERT a scoped_secrets row keyed by (org_id, scope=environment, key).
351
+ * UPSERT a scoped_secrets row keyed by (org_id, scope=context, key).
342
352
  * Writes the value verbatim — the caller is responsible for encryption
343
353
  * (matches the stage-4 deferral noted in the plan).
344
354
  */
345
- export declare function setEnvironmentSecretDirect(databaseUrl: string, opts: SetEnvironmentSecretOpts): Promise<{
355
+ export declare function setContextSecretDirect(databaseUrl: string, opts: SetContextSecretOpts): Promise<{
346
356
  inserted: boolean;
347
357
  }>;
348
358
  export interface DispatchQueueRow {
@@ -400,7 +410,7 @@ export interface ExecutionRunRow {
400
410
  ref: string;
401
411
  sha: string;
402
412
  routing_key: string | null;
403
- environment: string | null;
413
+ context: string | null;
404
414
  trust_tier: string | null;
405
415
  created_at: string;
406
416
  started_at: string;
@@ -419,8 +429,8 @@ export interface ExecutionJobRow {
419
429
  duration_ms: number | null;
420
430
  created_at: string;
421
431
  error_message: string | null;
422
- /** Ordered bound deployment-environment names (JSON-encoded `string[]`), or null. */
423
- environments: string | null;
432
+ /** Ordered bound deployment-context names (JSON-encoded `string[]`), or null. */
433
+ contexts: string | null;
424
434
  }
425
435
  export interface ListExecutionRunsOpts {
426
436
  routingKey?: string;
@@ -435,6 +445,79 @@ export interface ListExecutionRunsOpts {
435
445
  export declare function listExecutionRunsDirect(databaseUrl: string, opts?: ListExecutionRunsOpts): Promise<{
436
446
  runs: ExecutionRunRow[];
437
447
  }>;
448
+ /**
449
+ * One `check_run_tracking` row: the orchestrator's record of a check run it posted.
450
+ *
451
+ * Named `...DirectRow` rather than `CheckRunTrackingRow` because the
452
+ * orchestrator's own `db/types.ts` already exports that name for the Kysely
453
+ * `Selectable`, whose `check_run_id` is a `number`. Two same-named types with
454
+ * different field types, both in scope inside the orchestrator package, is a
455
+ * silent-comparison-bug waiting to happen.
456
+ */
457
+ export interface CheckRunTrackingDirectRow {
458
+ provider: string;
459
+ owner: string;
460
+ repo: string;
461
+ sha: string;
462
+ check_name: string;
463
+ /**
464
+ * The id the provider returned when the check run was CREATED. It is written
465
+ * once, at create time, when the check run is still `queued` — the later
466
+ * terminal update is a PATCH that writes nothing here. So a non-null value
467
+ * proves creation, NOT that the check run reached a conclusion.
468
+ *
469
+ * Null does not prove the create failed either: the write is best-effort and
470
+ * falls back to cache-only on a DB error, so the check run can exist at the
471
+ * provider with no id recorded here.
472
+ *
473
+ * Selected as `::text` because the column is BIGINT and node-postgres maps
474
+ * int8 to a string to avoid precision loss. The cast makes that explicit in
475
+ * the query rather than depending on driver defaults, so adding a global
476
+ * int8 type parser later cannot silently change this field's type.
477
+ */
478
+ check_run_id: string | null;
479
+ /**
480
+ * `'pending'` is stamped BEFORE the create call and `'completed'` after it
481
+ * returns an id. Nothing resets it when a create fails, so a row stuck on
482
+ * `'pending'` means the create never returned — still in flight or
483
+ * permanently failed, which this column alone cannot distinguish.
484
+ */
485
+ build_creation_state: string | null;
486
+ run_id: string | null;
487
+ /**
488
+ * Written only for per-job check names (`kici/<workflow>/job/<job>`). The
489
+ * workflow-level `kici/<workflow>` row always has null here.
490
+ */
491
+ in_progress_sent_at: Date | null;
492
+ /**
493
+ * When the terminal (`completed`) update was accepted by the provider. This
494
+ * is the column that answers "did we complete it?" — `check_run_id` only
495
+ * answers "did we create it?".
496
+ *
497
+ * Best-effort like every write on this table, so null means "we have no
498
+ * record of sending it", not "it was never sent".
499
+ */
500
+ terminal_sent_at: Date | null;
501
+ }
502
+ export interface ListCheckRunTrackingOpts {
503
+ sha: string;
504
+ checkName?: string;
505
+ limit?: number;
506
+ }
507
+ /**
508
+ * READ-ONLY: SELECT check_run_tracking rows for a commit. One row per
509
+ * `(provider, owner, repo, sha, check_name)`, ordered by check_name.
510
+ *
511
+ * Each column answers a different question. `check_run_id` is written once,
512
+ * when the check run is created in the `queued` state, so it answers "did we
513
+ * create it?". `terminal_sent_at` is stamped only after the provider accepts
514
+ * the terminal `completed` PATCH, so it answers "did we complete it?". Every
515
+ * write here is best-effort, so a null column is "no record", never proof of
516
+ * failure — see the per-field notes for what each one does and does not prove.
517
+ */
518
+ export declare function listCheckRunTrackingDirect(databaseUrl: string, opts: ListCheckRunTrackingOpts): Promise<{
519
+ rows: CheckRunTrackingDirectRow[];
520
+ }>;
438
521
  /**
439
522
  * READ-ONLY: fetch a single run by run_id AND its jobs. Throws if no run
440
523
  * matches the run_id. Jobs list may be empty for pending runs.
@@ -869,7 +952,7 @@ export interface SeedUniversalGitSourceOpts {
869
952
  export declare function seedUniversalGitSourceDirect(databaseUrl: string, opts: SeedUniversalGitSourceOpts): Promise<void>;
870
953
  /**
871
954
  * Seed the ci-security orchestrator fixtures expected by the security
872
- * pipeline e2e: sources row for dashboard orgId resolution, environment,
955
+ * pipeline e2e: sources row for dashboard orgId resolution, context,
873
956
  * two execution_runs (unknown + trusted), two execution_jobs, and a
874
957
  * security held_run for the unknown contributor.
875
958
  *
@@ -877,7 +960,7 @@ export declare function seedUniversalGitSourceDirect(databaseUrl: string, opts:
877
960
  */
878
961
  export interface SeedCiSecurityFixturesOpts {
879
962
  orgId: string;
880
- envName?: string;
963
+ contextName?: string;
881
964
  sourceName?: string;
882
965
  sourceRoutingKey?: string;
883
966
  runsRoutingKey: string;
@@ -887,24 +970,80 @@ export interface SeedCiSecurityFixturesOpts {
887
970
  trustedRunId: string;
888
971
  trustedDeliveryId: string;
889
972
  trustedJobId: string;
973
+ /**
974
+ * Second PR on the SAME repo (`repo_identifier='.'`, `pr_number=2`) with its
975
+ * own pending security hold. Seeded so PR-scoping tests can prove that a
976
+ * `/kici approve` on the first PR (pr_number=1) leaves this one held.
977
+ */
978
+ secondPrRunId: string;
979
+ secondPrDeliveryId: string;
980
+ secondPrJobId: string;
981
+ /**
982
+ * Hold on a DIFFERENT repo (`repo_identifier='other/repo'`, `pr_number=1`) —
983
+ * same PR number as the first hold but a different repo, proving the scoping
984
+ * isolates on repo as well as PR number.
985
+ */
986
+ otherRepoRunId: string;
987
+ otherRepoDeliveryId: string;
988
+ otherRepoJobId: string;
989
+ /**
990
+ * Workflow-modification hold (`repo_identifier='.'`, `pr_number=3`,
991
+ * `reason='workflow_modification'`) — the hold a non-trusted contributor's
992
+ * workflow-editing PR produces. Seeded so the PR-scoped `/kici approve`
993
+ * (which joins on `pr_number`) can find and resolve it.
994
+ */
995
+ wfModRunId: string;
996
+ wfModDeliveryId: string;
997
+ wfModJobId: string;
998
+ /**
999
+ * Fork-PR hold (`repo_identifier='.'`, `pr_number=4`, `reason='fork_pr'`) —
1000
+ * the hold the org trust policy's fork arm produces. Reachable only since the
1001
+ * policy became enforced, so it is seeded to prove PR-scoped selection and
1002
+ * approval work for it exactly as they do for the workflow-modification hold.
1003
+ */
1004
+ forkPrRunId: string;
1005
+ forkPrDeliveryId: string;
1006
+ forkPrJobId: string;
890
1007
  }
891
1008
  export interface SeedCiSecurityFixturesResult {
1009
+ /**
1010
+ * The context name the fixture seeded (the `contextName` option, or its
1011
+ * default). Assertions build the expected `held_runs.reason` from this rather
1012
+ * than re-deriving the name, so an override cannot desync them.
1013
+ */
1014
+ contextName: string;
892
1015
  envId: string;
1016
+ /** Held run for the unknown contributor (repo `.`, pr_number 1). */
893
1017
  heldRunId: string;
1018
+ /** Held run for the same-repo second PR (repo `.`, pr_number 2). */
1019
+ secondHeldRunId: string;
1020
+ /** Held run for the different-repo run (repo `other/repo`, pr_number 1). */
1021
+ otherRepoHeldRunId: string;
1022
+ /** Held run for the workflow-modification PR (repo `.`, pr_number 3). */
1023
+ wfModHeldRunId: string;
1024
+ /** Held run for the fork PR (repo `.`, pr_number 4). */
1025
+ forkPrHeldRunId: string;
894
1026
  }
1027
+ /** repo_identifier used for the different-repo isolation hold. */
1028
+ export declare const CI_SECURITY_OTHER_REPO = "other/repo";
895
1029
  export declare function seedCiSecurityFixturesDirect(databaseUrl: string, opts: SeedCiSecurityFixturesOpts): Promise<SeedCiSecurityFixturesResult>;
896
1030
  /**
897
- * Poll `execution_runs` for at least one row matching `status` whose
898
- * `started_at > since`. Used by cluster/job-reroute tests to gate on
899
- * a workflow reaching the terminal state after a webhook trigger.
1031
+ * Poll `execution_runs` for the newest run started since `since` whose status
1032
+ * is in `statuses`, returning that status. Resolves `{ status: null }` if the
1033
+ * deadline passes before any run reaches a target status.
1034
+ *
1035
+ * Callers wanting "did the run finish?" pass the terminal status set and read
1036
+ * the landed status — a terminal failure is reported immediately rather than
1037
+ * indistinguishable from a timeout. Used by the cluster reroute tests to gate
1038
+ * on a workflow reaching a terminal state after a webhook trigger.
900
1039
  */
901
- export declare function waitForExecutionRunStatusSinceDirect(databaseUrl: string, opts: {
902
- status: string;
1040
+ export declare function waitForExecutionRunReachesStatusSinceDirect(databaseUrl: string, opts: {
903
1041
  since: Date;
1042
+ statuses: readonly string[];
904
1043
  timeoutMs?: number;
905
1044
  intervalMs?: number;
906
1045
  }): Promise<{
907
- found: boolean;
1046
+ status: string | null;
908
1047
  }>;
909
1048
  /**
910
1049
  * Fetch the most recent `execution_runs` row matching `status`, plus its
@@ -1250,6 +1389,7 @@ export declare function bumpRegistryVersionSimpleDirect(databaseUrl: string, opt
1250
1389
  export declare function upsertCronLastFiredDirect(databaseUrl: string, opts: {
1251
1390
  registrationId: string;
1252
1391
  agoInterval: string;
1392
+ scheduleKey: string;
1253
1393
  }): Promise<void>;
1254
1394
  /**
1255
1395
  * READ-ONLY: count cron_last_fired rows for a registration. Used by
@@ -1264,6 +1404,7 @@ export declare function countCronLastFiredDirect(databaseUrl: string, opts: {
1264
1404
  */
1265
1405
  export declare function insertCronLastFiredNowDirect(databaseUrl: string, opts: {
1266
1406
  registrationId: string;
1407
+ scheduleKey: string;
1267
1408
  }): Promise<void>;
1268
1409
  /**
1269
1410
  * DELETE cron_last_fired rows for a registration. Teardown helper.