@kici-dev/orchestrator 0.1.27 → 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.
Files changed (270) hide show
  1. package/dist/__test-helpers__/mock-db.d.ts +2 -0
  2. package/dist/agent/dispatcher.d.ts +52 -2
  3. package/dist/agent/host-roster-reaper.d.ts +1 -2
  4. package/dist/agent/host-roster.d.ts +34 -0
  5. package/dist/agent/ownership-refusal.d.ts +12 -0
  6. package/dist/agent/ownership-tracker.d.ts +64 -16
  7. package/dist/agent/token-store.d.ts +13 -0
  8. package/dist/agent-packaging/availability.d.ts +27 -0
  9. package/dist/agent-packaging/build-package.d.ts +16 -0
  10. package/dist/agent-packaging/download.d.ts +30 -0
  11. package/dist/agent-packaging/index.d.ts +9 -0
  12. package/dist/agent-packaging/install-closure.d.ts +30 -0
  13. package/dist/agent-packaging/node-binary.d.ts +26 -0
  14. package/dist/agent-packaging/refresh.d.ts +50 -0
  15. package/dist/agent-packaging/store.d.ts +15 -0
  16. package/dist/agent-packaging/upload.d.ts +24 -0
  17. package/dist/app-on-error.d.ts +29 -0
  18. package/dist/app.d.ts +56 -4
  19. package/dist/artifacts/artifact-store.d.ts +233 -0
  20. package/dist/artifacts/failure-messages.d.ts +46 -0
  21. package/dist/audit/access-log.d.ts +18 -1
  22. package/dist/cache/dep-cache.d.ts +15 -0
  23. package/dist/cache/user-cache.d.ts +37 -0
  24. package/dist/cancel/cancel-run.d.ts +10 -3
  25. package/dist/cancel/dashboard-cancel-handler.d.ts +19 -0
  26. package/dist/cli/api-client.d.ts +22 -1
  27. package/dist/cli/commands/agent-package.d.ts +34 -0
  28. package/dist/cli/commands/check-run.d.ts +26 -0
  29. package/dist/cli/commands/cluster-settings.d.ts +55 -0
  30. package/dist/cli/commands/dashboard-encryption-key.d.ts +3 -0
  31. package/dist/cli/commands/db-backup.d.ts +71 -0
  32. package/dist/cli/commands/orchestrator-service/drain.d.ts +31 -0
  33. package/dist/cli/commands/orchestrator-service/index.d.ts +7 -4
  34. package/dist/cli/commands/orchestrator-service/install-env.d.ts +58 -0
  35. package/dist/cli/commands/secret.d.ts +45 -1
  36. package/dist/cli/commands/shared/admin-cli-access-log.d.ts +46 -0
  37. package/dist/cli/commands/shared/versioned-upgrade.d.ts +65 -2
  38. package/dist/cli/commands/signing-key.d.ts +3 -0
  39. package/dist/cli/commands/source.d.ts +12 -0
  40. package/dist/cli/commands/token.d.ts +9 -0
  41. package/dist/cli/commands/trust-policy.d.ts +48 -0
  42. package/dist/cli/join.d.ts +1 -1
  43. package/dist/cli/service/index.d.ts +2 -2
  44. package/dist/cli/service/instance/manifest.d.ts +17 -0
  45. package/dist/cli/service/instance/resolve.d.ts +11 -0
  46. package/dist/cli/wizard/orchestrator-wizard.d.ts +39 -11
  47. package/dist/cli.js +9818 -3408
  48. package/dist/cluster/cluster-settings-reader.d.ts +78 -0
  49. package/dist/cluster/coordinator.d.ts +81 -9
  50. package/dist/cluster/join-client.d.ts +1 -1
  51. package/dist/cluster/leader-gated-scheduler.d.ts +50 -0
  52. package/dist/cluster/orphan-recovery.d.ts +14 -0
  53. package/dist/cluster/peer-client.d.ts +12 -2
  54. package/dist/cluster/peer-handler.d.ts +15 -4
  55. package/dist/cluster/peer-registry.d.ts +12 -0
  56. package/dist/cluster/verified-issuer-poller.d.ts +11 -0
  57. package/dist/cluster/verified-issuer.d.ts +63 -0
  58. package/dist/cold-store/tables/access-log.d.ts +1 -1
  59. package/dist/cold-store/tables/event-log.d.ts +1 -1
  60. package/dist/cold-store/tables/execution-jobs.d.ts +1 -1
  61. package/dist/cold-store/tables/execution-runs.d.ts +1 -1
  62. package/dist/cold-store/tables/execution-steps.d.ts +1 -1
  63. package/dist/cold-store/tables/secret-audit-log.d.ts +1 -1
  64. package/dist/config/loader.d.ts +1 -1
  65. package/dist/config/resolver.d.ts +2 -1
  66. package/dist/config/schema.d.ts +8 -6
  67. package/dist/config/types.d.ts +3 -2
  68. package/dist/config.d.ts +363 -53
  69. package/dist/contexts/context-store.d.ts +21 -8
  70. package/dist/contexts/glob-specificity.d.ts +30 -0
  71. package/dist/contexts/held-runs.d.ts +51 -19
  72. package/dist/contexts/protection/aggregate.d.ts +2 -2
  73. package/dist/contexts/protection/concurrency-gate.d.ts +0 -3
  74. package/dist/contexts/protection/reviewer-gate.d.ts +0 -3
  75. package/dist/contexts/protection/satisfiability.d.ts +1 -1
  76. package/dist/contexts/protection/trust-gate.d.ts +0 -3
  77. package/dist/contexts/protection/wait-timer-gate.d.ts +0 -3
  78. package/dist/cron/cron-scheduler.d.ts +24 -4
  79. package/dist/cron/cron-store.d.ts +21 -7
  80. package/dist/dashboard/attestation-filters.d.ts +92 -74
  81. package/dist/dashboard/handler.d.ts +35 -1
  82. package/dist/db/migration-test-harness.d.ts +41 -0
  83. package/dist/db/migrations/069_reroute_tunables.d.ts +21 -0
  84. package/dist/db/migrations/070_execution_runs_failure_class.d.ts +4 -0
  85. package/dist/db/migrations/071_batch_accumulation.d.ts +26 -0
  86. package/dist/db/migrations/072_dispatch_queue_run_id_index.d.ts +15 -0
  87. package/dist/db/migrations/073_org_settings_ingest_concurrency.d.ts +13 -0
  88. package/dist/db/migrations/074_normalize_zero_concurrency_limit.d.ts +13 -0
  89. package/dist/db/migrations/075_ingest_overflow_buffer.d.ts +16 -0
  90. package/dist/db/migrations/076_artifacts.d.ts +19 -0
  91. package/dist/db/migrations/077_backup_runs.d.ts +13 -0
  92. package/dist/db/migrations/078_org_settings_backup_staleness.d.ts +12 -0
  93. package/dist/db/migrations/079_org_settings_scaler_spawn_timeout.d.ts +14 -0
  94. package/dist/db/migrations/080_cluster_settings.d.ts +17 -0
  95. package/dist/db/migrations/081_org_settings_queue_timeout.d.ts +19 -0
  96. package/dist/db/migrations/082_host_s3_reachable.d.ts +16 -0
  97. package/dist/db/migrations/083_org_settings_artifact_caps.d.ts +18 -0
  98. package/dist/db/migrations/084_orchestrator_signing_keys.d.ts +20 -0
  99. package/dist/db/migrations/085_cluster_settings_reroute_flap_grace_ms.d.ts +12 -0
  100. package/dist/db/migrations/086_cluster_settings_max_fanout_hosts.d.ts +12 -0
  101. package/dist/db/migrations/087_cluster_settings_event_router_rate_limit.d.ts +12 -0
  102. package/dist/db/migrations/088_cluster_settings_cache_max_tarball_bytes.d.ts +12 -0
  103. package/dist/db/migrations/089_cluster_settings_cache_ttl_days.d.ts +12 -0
  104. package/dist/db/migrations/090_cluster_settings_concurrency_wait_timeout_ms.d.ts +12 -0
  105. package/dist/db/migrations/091_cluster_settings_agent_token_ttl_ms.d.ts +12 -0
  106. package/dist/db/migrations/092_cluster_settings_version.d.ts +16 -0
  107. package/dist/db/migrations/093_org_settings_sandbox_allowlist.d.ts +4 -0
  108. package/dist/db/migrations/094_dashboard_encryption_keys.d.ts +21 -0
  109. package/dist/db/migrations/095_dashboard_write_policy_tristate.d.ts +20 -0
  110. package/dist/db/migrations/096_multi_schedule_cron_last_fired.d.ts +4 -0
  111. package/dist/db/migrations/097_execution_runs_pr_number.d.ts +4 -0
  112. package/dist/db/migrations/098_execution_runs_customer_id.d.ts +4 -0
  113. package/dist/db/migrations/099_cluster_settings_dashboard_verified_issuer.d.ts +21 -0
  114. package/dist/db/migrations/100_held_runs_hold_type_vocabulary.d.ts +31 -0
  115. package/dist/db/migrations/101_contexts_hold_expiry_drop_default.d.ts +31 -0
  116. package/dist/db/migrations/102_dispatch_queue_agent_id.d.ts +20 -0
  117. package/dist/db/migrations/103_cluster_settings_ownership_db_check_timeout_ms.d.ts +20 -0
  118. package/dist/db/migrations/104_check_run_terminal_sent.d.ts +22 -0
  119. package/dist/db/migrations/105_org_trust_policy.d.ts +19 -0
  120. package/dist/db/migrations/106_cluster_settings_check_run_tracking_ttl_days.d.ts +18 -0
  121. package/dist/db/migrations/107_check_run_tracking_updated_at_index.d.ts +23 -0
  122. package/dist/db/repos/dashboard-encryption-keys-repo.d.ts +48 -0
  123. package/dist/db/repos/signing-keys-repo.d.ts +34 -0
  124. package/dist/db/types.d.ts +421 -7
  125. package/dist/deployment/deployment-identity.d.ts +87 -2
  126. package/dist/diagnostics/checks/backup.d.ts +18 -0
  127. package/dist/diagnostics/checks/index.d.ts +2 -1
  128. package/dist/diagnostics/fleet-collector.d.ts +1 -1
  129. package/dist/drain/drain-controller.d.ts +52 -0
  130. package/dist/events/batch-accumulator.d.ts +63 -0
  131. package/dist/events/circuit-breaker.d.ts +9 -3
  132. package/dist/events/event-emitter.d.ts +31 -0
  133. package/dist/events/event-retry-scanner.d.ts +12 -2
  134. package/dist/events/event-router.d.ts +29 -0
  135. package/dist/firecracker/host-network.d.ts +2 -0
  136. package/dist/firecracker/net-interfaces.d.ts +14 -0
  137. package/dist/github-app-name-refresher/github-app-name-refresher.d.ts +18 -8
  138. package/dist/index.d.ts +2 -0
  139. package/dist/index.js +604 -16
  140. package/dist/lockfile-cache.d.ts +4 -0
  141. package/dist/lockfile-validate.d.ts +16 -9
  142. package/dist/metrics/prometheus.d.ts +112 -15
  143. package/dist/oidc/aws-kms-signer.d.ts +37 -0
  144. package/dist/oidc/command-signer.d.ts +23 -0
  145. package/dist/oidc/db-signer.d.ts +48 -0
  146. package/dist/oidc/id-token-claims.d.ts +22 -9
  147. package/dist/oidc/jwt.d.ts +5 -2
  148. package/dist/oidc/local-dev-signer.d.ts +1 -1
  149. package/dist/oidc/oidc-mint-registration.d.ts +27 -11
  150. package/dist/oidc/orchestrator-mint.d.ts +97 -0
  151. package/dist/oidc/orchestrator-signer-factory.d.ts +33 -0
  152. package/dist/oidc/reconcile-signing-key.d.ts +21 -0
  153. package/dist/oidc/signer.d.ts +25 -0
  154. package/dist/oidc/signing-key-status.d.ts +23 -0
  155. package/dist/orchestrator-core.d.ts +42 -2
  156. package/dist/pipeline/decorating-secret-resolver.d.ts +3 -2
  157. package/dist/pipeline/dispatch-matched-workflow.d.ts +41 -0
  158. package/dist/pipeline/install-secrets-resolver.d.ts +12 -1
  159. package/dist/pipeline/manual-schedule.d.ts +20 -13
  160. package/dist/pipeline/pending-workflow-context.d.ts +1 -1
  161. package/dist/pipeline/process-webhook.d.ts +57 -2
  162. package/dist/pipeline/processor.d.ts +44 -8
  163. package/dist/pipeline/rerun.d.ts +12 -13
  164. package/dist/pipeline/resolve-sandbox-grant.d.ts +28 -0
  165. package/dist/pipeline/route-or-dispatch-jobs.d.ts +71 -0
  166. package/dist/pipeline/sandbox-allowlist-reader.d.ts +33 -0
  167. package/dist/policy/dashboard-write-policy.d.ts +9 -4
  168. package/dist/provenance/backfill-run.d.ts +1 -0
  169. package/dist/provenance/trust-root.d.ts +13 -0
  170. package/dist/providers/generic/normalizer.d.ts +1 -1
  171. package/dist/providers/github/auth.d.ts +1 -1
  172. package/dist/providers/github/changed-files.d.ts +14 -6
  173. package/dist/providers/github/check-status-poster.d.ts +19 -3
  174. package/dist/providers/github/contributor-resolver.d.ts +1 -1
  175. package/dist/providers/github/lock-file.d.ts +1 -1
  176. package/dist/providers/github/normalizer.d.ts +1 -1
  177. package/dist/providers/github/repo-url.d.ts +1 -1
  178. package/dist/providers/local/lock-file-fetcher.d.ts +1 -1
  179. package/dist/providers/local/normalizer.d.ts +1 -1
  180. package/dist/providers/local/repo-url-builder.d.ts +1 -1
  181. package/dist/providers/universal-git/changed-files.d.ts +8 -7
  182. package/dist/providers/universal-git/clone-token.d.ts +1 -1
  183. package/dist/providers/universal-git/config.d.ts +4 -4
  184. package/dist/providers/universal-git/index.d.ts +5 -1
  185. package/dist/providers/universal-git/lock-file.d.ts +6 -1
  186. package/dist/providers/universal-git/normalizer.d.ts +1 -1
  187. package/dist/providers/universal-git/repo-url.d.ts +1 -1
  188. package/dist/queue/cleanup.d.ts +69 -8
  189. package/dist/queue/job-queue.d.ts +94 -14
  190. package/dist/queue/scheduled-job.d.ts +2 -2
  191. package/dist/registration/registration-index.d.ts +1 -1
  192. package/dist/reporting/agent-failure-category.d.ts +4 -1
  193. package/dist/reporting/check-run-reporter.d.ts +34 -14
  194. package/dist/reporting/check-run-tracking-store.d.ts +70 -8
  195. package/dist/reporting/execution-tracker.d.ts +156 -10
  196. package/dist/reporting/fs-log-storage.d.ts +31 -2
  197. package/dist/reporting/job-check-run-completion.d.ts +50 -0
  198. package/dist/reporting/log-chunk-sink.d.ts +47 -0
  199. package/dist/reporting/log-retention.d.ts +16 -0
  200. package/dist/reporting/log-storage.d.ts +64 -4
  201. package/dist/reporting/log-writer.d.ts +22 -7
  202. package/dist/reporting/peer-log-normalize.d.ts +14 -0
  203. package/dist/reporting/run-aggregator.d.ts +32 -32
  204. package/dist/reporting/s3-log-storage.d.ts +102 -8
  205. package/dist/routes/admin-auth.d.ts +90 -0
  206. package/dist/routes/admin-cluster-settings.d.ts +31 -0
  207. package/dist/routes/admin-events.d.ts +3 -0
  208. package/dist/routes/admin-orchestrator-drain.d.ts +35 -0
  209. package/dist/routes/admin-runs.d.ts +9 -0
  210. package/dist/routes/admin-sources.d.ts +7 -0
  211. package/dist/routes/admin-trust-policy.d.ts +49 -0
  212. package/dist/routes/admin.d.ts +15 -1
  213. package/dist/routes/github-webhook.d.ts +9 -3
  214. package/dist/routes/provenance-oidc.d.ts +38 -0
  215. package/dist/routes/verify-attestation.d.ts +21 -0
  216. package/dist/scaler/bare-metal-backend.d.ts +9 -2
  217. package/dist/scaler/config.d.ts +25 -4
  218. package/dist/scaler/container-backend.d.ts +9 -1
  219. package/dist/scaler/firecracker-backend.d.ts +29 -2
  220. package/dist/scaler/index.d.ts +1 -1
  221. package/dist/scaler/manager.d.ts +166 -8
  222. package/dist/scaler/pending-scale-sweeper.d.ts +35 -0
  223. package/dist/scaler/types.d.ts +75 -6
  224. package/dist/secrets/backend-registry.d.ts +32 -1
  225. package/dist/secrets/dashboard-encryption-key.d.ts +48 -0
  226. package/dist/secrets/ephemeral-keys.d.ts +16 -0
  227. package/dist/secrets/pg-secret-store.d.ts +10 -0
  228. package/dist/secrets/rbac.d.ts +5 -1
  229. package/dist/secrets/routing-key-scope.d.ts +6 -0
  230. package/dist/secrets/scope-routing.d.ts +126 -0
  231. package/dist/secrets/secret-resolver.d.ts +1 -14
  232. package/dist/secrets/token-manager.d.ts +5 -1
  233. package/dist/security/contributor-cache.d.ts +9 -1
  234. package/dist/security/trust-policy-gate.d.ts +117 -0
  235. package/dist/security/trust-policy-store.d.ts +78 -0
  236. package/dist/server.js +35769 -25052
  237. package/dist/sources/source-manager.d.ts +24 -0
  238. package/dist/stale-detector/stale-run-detector.d.ts +7 -0
  239. package/dist/standalone.js +34831 -25317
  240. package/dist/storage/filesystem.d.ts +17 -0
  241. package/dist/storage/key-discriminator.d.ts +20 -0
  242. package/dist/storage/s3.d.ts +2 -0
  243. package/dist/storage/types.d.ts +16 -0
  244. package/dist/webhook/dedup.d.ts +9 -1
  245. package/dist/webhook/event-log.d.ts +13 -2
  246. package/dist/webhook/generic-sources-listener.d.ts +4 -0
  247. package/dist/webhook/ingest-admission.d.ts +140 -0
  248. package/dist/webhook/ingest-overflow-buffer.d.ts +27 -0
  249. package/dist/webhook/ingest-overflow-replayer.d.ts +49 -0
  250. package/dist/webhook/ingest-overflow-types.d.ts +73 -0
  251. package/dist/webhook/loop-lag-source.d.ts +40 -0
  252. package/dist/webhook/observed-relay-guard.d.ts +40 -0
  253. package/dist/webhook/org-ingest-cap-reader.d.ts +24 -0
  254. package/dist/webhook/register-source-bundle.d.ts +3 -0
  255. package/dist/webhook/relay-reinject.d.ts +43 -0
  256. package/dist/worker/in-memory-job-queue.d.ts +8 -3
  257. package/dist/worker-core.d.ts +10 -0
  258. package/dist/ws/agent-api-registry.d.ts +20 -0
  259. package/dist/ws/agent-handler.d.ts +63 -7
  260. package/dist/ws/bringup-api.d.ts +99 -0
  261. package/dist/ws/dashboard-context-handler.d.ts +28 -10
  262. package/dist/ws/dashboard-run-state-handler.d.ts +31 -0
  263. package/dist/ws/failure-messages.d.ts +29 -0
  264. package/dist/ws/oidc-token-relay.d.ts +9 -0
  265. package/dist/ws/ownership-gate.d.ts +22 -0
  266. package/dist/ws/platform-client.d.ts +96 -1
  267. package/dist/ws/replay-chunker.d.ts +37 -0
  268. package/installer-image-digests.json +3 -3
  269. package/package.json +22 -21
  270. package/sbom.spdx.json +1240 -1788
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { type Kysely } from 'kysely';
15
15
  import type { Database } from '../db/types.js';
16
- import { ExecutionRunStatus, type InitFailure, ScalerEventType } from '@kici-dev/engine';
16
+ import { ExecutionRunStatus, type InitFailure, RunFailureClass, ScalerEventType, OrchLogPhase } from '@kici-dev/engine';
17
17
  import type { ObserverRegistry } from '../ws/observer-registry.js';
18
18
  import type { LogStorage } from './log-storage.js';
19
19
  import type { JobQueue } from '../queue/job-queue.js';
@@ -50,6 +50,12 @@ export interface ExecutionContext {
50
50
  triggerActorUsername?: string | null;
51
51
  /** Immutable provider user id of the triggering actor (preferred for resolution). */
52
52
  triggerActorUserId?: string | null;
53
+ /**
54
+ * Why a terminal run failed (`RunFailureClass`). Set on terminal-failed /
55
+ * cancelled runs; forwarded to the Platform on `execution.status` so managed
56
+ * subscriptions can match on it. Null/undefined for success or non-terminal.
57
+ */
58
+ failureClass?: RunFailureClass | null;
53
59
  /** Workflow-level concurrency config from the lock file. */
54
60
  concurrency?: {
55
61
  cancelInProgress?: boolean;
@@ -68,6 +74,8 @@ interface WorkflowCompleteCallbackData {
68
74
  }>;
69
75
  routingKey?: string;
70
76
  repo: string;
77
+ /** Why the run failed (`RunFailureClass`); carried onto the `__workflow_complete` event. */
78
+ failureClass?: string;
71
79
  }
72
80
  /** Data passed to the onJobComplete callback. */
73
81
  interface JobCompleteCallbackData {
@@ -78,6 +86,11 @@ interface JobCompleteCallbackData {
78
86
  routingKey?: string;
79
87
  repo: string;
80
88
  workflowName: string;
89
+ /**
90
+ * Job payload carried by the status update (step results, error, duration).
91
+ * Feeds the enriched check-run summary and the failure description.
92
+ */
93
+ data?: Record<string, unknown>;
81
94
  }
82
95
  export interface ExecutionTrackerDeps {
83
96
  db: Kysely<Database>;
@@ -145,12 +158,37 @@ export interface ExecutionTrackerDeps {
145
158
  metadata?: Record<string, unknown>;
146
159
  durationMs?: number | null;
147
160
  }) => void;
161
+ /**
162
+ * Optional live forward of orchestration/provisioning log lines to Platform,
163
+ * fired in addition to persisting them to `logStorage`. Platform fans these
164
+ * out to run-detail browser subscribers as `orch-log.lines`. Best-effort:
165
+ * a push failure must never break dispatch.
166
+ *
167
+ * SECURITY: like `onRunEventEmit`, this payload MUST NOT carry an `orgId`.
168
+ * The Platform attributes the tenant from `authState.orgId`.
169
+ */
170
+ onOrchLog?: (chunk: {
171
+ runId: string;
172
+ jobId: string;
173
+ phase: OrchLogPhase;
174
+ lines: string[];
175
+ ts: number;
176
+ }) => void;
148
177
  /** Optional log storage for writing per-job orchestration logs (JSONL). */
149
178
  logStorage?: LogStorage;
150
179
  /** Org ID for this orchestrator instance (used in run.event emission). */
151
180
  orgId?: string;
152
181
  /** Optional job queue for cascading run failures to dispatch_queue entries. */
153
182
  jobQueue?: JobQueue;
183
+ /**
184
+ * Resolves a routing key to its owning org (customer_id), used to populate
185
+ * `execution_runs.customer_id` at insert time so the concurrency-gate running
186
+ * count is scoped per tenant. Injected (bound to `(rk) => resolveOrgId(db, rk)`
187
+ * at construction) to keep the tracker free of a pipeline import cycle. When
188
+ * absent — or when a run has no routing key — the row falls back to the
189
+ * `'__default__'` column default (the no-source fallback org).
190
+ */
191
+ resolveOrgId?: (routingKey: string) => Promise<string>;
154
192
  }
155
193
  export declare class ExecutionTracker {
156
194
  private readonly db;
@@ -163,9 +201,11 @@ export declare class ExecutionTracker {
163
201
  private readonly onExecutionStatusChange?;
164
202
  private readonly onJobStatusChange?;
165
203
  private readonly onRunEventEmit?;
204
+ private readonly onOrchLog?;
166
205
  private readonly logStorage?;
167
206
  private readonly orgId?;
168
207
  private readonly jobQueue?;
208
+ private readonly resolveOrgIdFn?;
169
209
  private readonly runs;
170
210
  /**
171
211
  * Per-run async-mutex chain. `onJobStatus` and `addJobsToRun` mutate the same
@@ -200,6 +240,12 @@ export declare class ExecutionTracker {
200
240
  /** Callback fired when a job's needs are satisfied and it's ready for dispatch. */
201
241
  onJobReadyCallback?: (runId: string, jobName: string) => Promise<void>;
202
242
  constructor(deps: ExecutionTrackerDeps);
243
+ /**
244
+ * Resolve the owning org for an `execution_runs` row from its routing key.
245
+ * Falls back to the `'__default__'` org (the column default) when the run has
246
+ * no routing key or no resolver is injected (single-tenant / test contexts).
247
+ */
248
+ private resolveCustomerId;
203
249
  /**
204
250
  * Register a callback fired when a job's needs become satisfied.
205
251
  * The processor sets this to dispatch newly-ready jobs to agents.
@@ -254,7 +300,9 @@ export declare class ExecutionTracker {
254
300
  /** Immutable provider user id of the triggering actor. */
255
301
  triggerActorUserId?: string | null,
256
302
  /** Agent provenance label when triggered through an agent credential. */
257
- triggeredByAgentLabel?: string | null): Promise<void>;
303
+ triggeredByAgentLabel?: string | null,
304
+ /** Pull-request number for PR-triggered runs; null/omitted for non-PR runs. */
305
+ prNumber?: number | null): Promise<void>;
258
306
  /**
259
307
  * Upsert one execution_jobs row per dispatched job (idempotent on
260
308
  * (run_id, job_id) to tolerate a race with an early `onJobStatus`). The
@@ -438,12 +486,17 @@ export declare class ExecutionTracker {
438
486
  */
439
487
  onBuildFailedBeforeTracking(runId: string, workflowName: string, provider: string, repoIdentifier: string, ref: string, sha: string, deliveryId: string | null, providerContext: Record<string, unknown>, routingKey: string, triggerEvent?: string, commitMessage?: string, failureReason?: string, initFailure?: InitFailure): Promise<void>;
440
488
  /**
441
- * Insert a `failed` execution_runs row directly for an init failure that
442
- * occurred BEFORE onExecutionStarted ran (so no in-memory state exists
443
- * and no jobs were dispatched). Also writes the structured init_failure
444
- * signal and fires onExecutionStatusChange so Platform's projection picks
445
- * it up via the normal forward path. Idempotent: if a row already exists
446
- * for this runId, the insert is a no-op (ON CONFLICT DO NOTHING).
489
+ * Write a `failed` execution_runs row directly for an init failure, and drop
490
+ * any in-memory run so the recorded failure is the run's final word. Also
491
+ * writes the structured init_failure signal and fires onExecutionStatusChange
492
+ * so Platform's projection picks it up via the normal forward path.
493
+ *
494
+ * An existing row for this runId is overwritten while it is still live
495
+ * dispatch may already have registered the run before init failed — and left
496
+ * alone once it is terminal, so a run that genuinely finished is never
497
+ * rewritten as failed. When the guard leaves the row alone, the failure
498
+ * metric and the Platform forward are suppressed with it: this orchestrator
499
+ * did not record the init failure, so it must not report one.
447
500
  *
448
501
  * Closes the silent pre-run-failure gap — without this helper, secret /
449
502
  * install-secret / all-jobs-rejected early-exits in dispatch-matched-workflow
@@ -469,7 +522,11 @@ export declare class ExecutionTracker {
469
522
  * `execution_runs` row in the `held` state — alive and resumable — so the
470
523
  * dashboard run list surfaces the paused workflow. No jobs are tracked: the
471
524
  * workflow-scoped held_runs row + pending workflow context (written by the
472
- * caller) keep the run from being counted complete. Idempotent on runId.
525
+ * caller) keep the run from being counted complete, and any in-memory run is
526
+ * dropped so jobs registered before the gate cannot roll it up.
527
+ *
528
+ * An existing row for this runId is flipped to `held` while it is still live
529
+ * and left alone once it is terminal — a finished run is never re-held.
473
530
  */
474
531
  recordRunHeld(args: {
475
532
  runId: string;
@@ -485,6 +542,13 @@ export declare class ExecutionTracker {
485
542
  reason: string;
486
543
  triggerEvent?: string;
487
544
  commitMessage?: string;
545
+ /**
546
+ * Pull-request number for PR-triggered holds; null/omitted for non-PR runs.
547
+ * Stamped so PR-scoped `/kici approve|reject` (which joins `execution_runs`
548
+ * on `pr_number`) can attribute the held run to its PR — a NULL leaves a
549
+ * security hold fail-closed unreachable by the comment path.
550
+ */
551
+ prNumber?: number | null;
488
552
  }): Promise<void>;
489
553
  /**
490
554
  * Flip a `held` run back to `pending` so the resumed dispatch can proceed
@@ -524,6 +588,54 @@ export declare class ExecutionTracker {
524
588
  * Check if all tracked jobs in a run have reached terminal state.
525
589
  */
526
590
  isRunComplete(runId: string): boolean;
591
+ /**
592
+ * Take a token holding a run open while some of its jobs are still to be
593
+ * registered. Returns true when a token was taken, false for an unknown or
594
+ * already-completed run (nothing to hold).
595
+ *
596
+ * Three registration windows need this. A run whose source-pack `__build__`
597
+ * job is dispatched first is registered with that job ALONE, and its real
598
+ * jobs are only dispatched once the build finishes. A deferred init job and a
599
+ * deferred dynamic entry each register their jobs from a fire-and-forget task
600
+ * that outlives the dispatch call. In every case, without a token the already
601
+ * -registered jobs reaching a terminal state satisfies {@link isRunComplete},
602
+ * so the run is finalized early: a terminal run status is written, the
603
+ * provider check is posted, and the status is forwarded to the Platform — all
604
+ * before a single real job has run.
605
+ *
606
+ * The hold is a counter on the in-memory run, read only by
607
+ * {@link isRunComplete} — deliberately NOT a synthetic entry in the run's job
608
+ * map. The job map is what every outward projection enumerates (the Platform
609
+ * `state.replay` snapshot and its `execution_jobs` mirror, the reported job
610
+ * count, the workflow-complete job results, the active-run summary), so a
611
+ * marker parked there would surface as a phantom pending job on the dashboard
612
+ * and an off-by-one job count for the length of the window. A counter holds
613
+ * the completion check open without being visible to any of them, and it
614
+ * writes no `execution_jobs` row, so it never surfaces as a phantom job.
615
+ * A token whose holder never settles keeps the run `running` until the stale
616
+ * detector reaps it — the same backstop that covers a job that never
617
+ * reports.
618
+ *
619
+ * Each token must be paired with exactly one {@link releasePendingJobsHold}.
620
+ */
621
+ holdRunForPendingJobs(runId: string): boolean;
622
+ /**
623
+ * Drop one token taken by {@link holdRunForPendingJobs}, and finalize the run
624
+ * if dropping the LAST one left every remaining job terminal.
625
+ *
626
+ * The re-evaluation matters for the same reason it does in `addJobsToRun`:
627
+ * once the last token is gone nothing else drives a completion check (no
628
+ * further `job.status` messages are coming for already-finished jobs), so a
629
+ * run whose jobs all finished while a token was held would otherwise hang in
630
+ * `running` forever. Only the last release may finalize — while any other
631
+ * token is outstanding more jobs are still on their way.
632
+ *
633
+ * Idempotent — a no-op when no token is outstanding. The count clamps at
634
+ * zero so a stray extra release cannot drive it negative, which would
635
+ * permanently un-hold the run and stop a genuinely outstanding token from
636
+ * holding it open.
637
+ */
638
+ releasePendingJobsHold(runId: string): Promise<void>;
527
639
  /**
528
640
  * Get the current overall status of a run.
529
641
  */
@@ -594,6 +706,7 @@ export declare class ExecutionTracker {
594
706
  triggeredBy?: string | null;
595
707
  triggeredByAgentLabel?: string | null;
596
708
  failureReason?: string;
709
+ failureClass?: RunFailureClass;
597
710
  jobCount: number;
598
711
  startedAt: number;
599
712
  completedAt?: number;
@@ -669,13 +782,35 @@ export declare class ExecutionTracker {
669
782
  * stale detector when memRun is still tracked locally — writes the
670
783
  * execution_runs terminal row, fires Platform-forwarding +
671
784
  * workflow-complete callbacks, and schedules in-memory pruning.
785
+ *
786
+ * The terminal write is clobber-guarded. If the guard rejects it (the run
787
+ * already finished through the normal path), only the pruning happens: the
788
+ * recomputed status is not this orchestrator's record of the run, so neither
789
+ * the callbacks nor the completion metrics may claim it.
672
790
  */
673
791
  private completeRunFromMemoryState;
792
+ /**
793
+ * Drop a finished run's in-memory state after a grace period, so a late
794
+ * status/heartbeat arriving just after completion still finds its run.
795
+ *
796
+ * This is the single owner of run-lifetime state teardown, so every
797
+ * run-lifetime map is cleared here — including `testRunIds`: a run finished by
798
+ * the stale detector rather than the normal completion path would otherwise
799
+ * leave its observer-broadcast marker behind for the process's lifetime.
800
+ * (`runLockTails` is deliberately not touched: it is keyed by runId but scoped
801
+ * to a lock's holders, and `withRunLock` drops its own entry on release.)
802
+ */
803
+ private scheduleRunPrune;
674
804
  /**
675
805
  * Path B helper: complete a run using DB rows only (crash recovery /
676
806
  * pruned runs). Loads jobs + run row from execution_jobs/execution_runs,
677
807
  * checks the all-terminal predicate + run-state guard, then writes the
678
808
  * terminal row and fires Platform-forwarding callbacks.
809
+ *
810
+ * The run-state read and the terminal write are separate statements, so the
811
+ * write carries its own clobber guard. If that guard rejects it — a
812
+ * concurrent normal completion landed in between — the callbacks and the
813
+ * completion metrics are suppressed with it.
679
814
  */
680
815
  private completeRunFromDbFallback;
681
816
  /**
@@ -714,10 +849,21 @@ export declare class ExecutionTracker {
714
849
  *
715
850
  * Per locked decision:
716
851
  * - success ONLY if ALL jobs pass (skipped jobs count as success)
717
- * - failed if ANY job failed or timed_out_stale
852
+ * - failed if ANY job ended in a failure status (see `STATUS_FAILURE_CLASS`)
718
853
  * - cancelled if ANY job cancelled (and none failed)
719
854
  */
720
855
  private computeRunStatus;
856
+ /**
857
+ * Derive the failure class from a terminal run's job statuses. Null for
858
+ * success (and any non-terminal status). `timed_out_stale` / `drift_dropped`
859
+ * / `unroutable` jobs — a job that never dispatched, whose `runsOn` matched no
860
+ * agent, whose agent went silent, or that was dropped by a topology reroute —
861
+ * are infra-class and collapse to `timed_out`; any other `failed` run is a
862
+ * `step_failure` (a job actually ran and failed). `never_started` (init
863
+ * failure) and `dead_orchestrator` (Platform-detected) are stamped by their
864
+ * own paths, not here.
865
+ */
866
+ private computeFailureClass;
721
867
  }
722
868
  export {};
723
869
  //# sourceMappingURL=execution-tracker.d.ts.map
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Filesystem-backed log storage.
3
3
  *
4
- * Stores execution logs as files on disk. No TTL -- logs persist indefinitely.
5
- * Supports append-only writes (JSONL accumulation) and cursor-based pagination.
4
+ * Stores execution logs as files on disk. Lifecycle is operator-managed (the
5
+ * cleanup retention sweep runs on the S3 backend only). Supports append-only
6
+ * writes (JSONL accumulation) and cursor-based pagination.
6
7
  *
7
8
  * File layout:
8
9
  * {basePath}/executions/{runId}/job-{name}/step-{index}.log
@@ -14,6 +15,27 @@ interface FilesystemLogStorageOptions {
14
15
  export declare class FilesystemLogStorage implements LogStorage {
15
16
  private readonly basePath;
16
17
  constructor(options: FilesystemLogStorageOptions);
18
+ /**
19
+ * Resolve a logical log path to an absolute filesystem path, refusing
20
+ * anything that would address a file outside the storage root.
21
+ *
22
+ * Two checks, deliberately both. `assertSafeLogPath` is the effective gate on
23
+ * POSIX: once a path is relative and carries no `..` segment, `resolve()`
24
+ * cannot produce anything outside the root, so the comparison below is
25
+ * unreachable there. It is kept as a second, independent statement of the
26
+ * same invariant — it is what catches a resolver with extra escape hatches
27
+ * (win32 resolves a drive-relative `C:foo` against that drive's own cwd, not
28
+ * against the root), and it means relaxing the logical check cannot silently
29
+ * reopen an escape.
30
+ *
31
+ * Both checks are lexical — neither resolves symlinks. A symlink planted
32
+ * inside the log root still redirects a logically-clean path outside it;
33
+ * containing that would need a `realpath` on every call, and the log root is
34
+ * the orchestrator user's own directory.
35
+ *
36
+ * Every verb on this class funnels through here, so a new caller is
37
+ * contained on arrival.
38
+ */
17
39
  private fullPath;
18
40
  /**
19
41
  * Ensure the directory for a given file path exists.
@@ -33,9 +55,16 @@ export declare class FilesystemLogStorage implements LogStorage {
33
55
  */
34
56
  private listRecursive;
35
57
  append(path: string, data: string): Promise<void>;
58
+ appendStreaming(path: string, data: string): Promise<void>;
59
+ finalize(_path: string): Promise<void>;
36
60
  read(path: string, options?: LogReadOptions): Promise<LogReadResult>;
37
61
  exists(path: string): Promise<boolean>;
38
62
  list(prefix: string): Promise<string[]>;
63
+ listWithMetadata(prefix: string): Promise<Array<{
64
+ path: string;
65
+ lastModified: Date;
66
+ }>>;
67
+ deleteMany(paths: string[]): Promise<number>;
39
68
  }
40
69
  export {};
41
70
  //# sourceMappingURL=fs-log-storage.d.ts.map
@@ -0,0 +1,50 @@
1
+ import { type CheckRunReporter } from './check-run-reporter.js';
2
+ import type { ExecutionContext } from './execution-tracker.js';
3
+ /**
4
+ * Job-terminal signal this module reacts to. Shaped after the payload the
5
+ * execution tracker hands its `onJobComplete` hook — its primary caller — plus
6
+ * the agent-supplied `data` blob used to build the enriched check-run summary.
7
+ */
8
+ export interface JobCheckRunCompletionInput {
9
+ runId: string;
10
+ jobId: string;
11
+ jobName: string;
12
+ /** Raw job status string as recorded by the tracker. */
13
+ status: string;
14
+ /** Agent-supplied job payload (step results, error, duration). */
15
+ data?: Record<string, unknown>;
16
+ /**
17
+ * Explicit check-run description, overriding the conclusion mapper's default
18
+ * wording. A caller that already holds the specific reason a job ended the way
19
+ * it did passes it here — the queue-expiry sweep's `unroutable` message, for
20
+ * instance, is the only statement of WHICH `runsOn` selectors went unmatched,
21
+ * and the mapper's generic phrasing cannot name them.
22
+ */
23
+ description?: string;
24
+ }
25
+ /** Collaborators needed to post a job's terminal check-run conclusion. */
26
+ export interface JobCheckRunCompletionDeps {
27
+ checkRunReporter: Pick<CheckRunReporter, 'updateJobStatus'>;
28
+ /** Resolves the run's provider/repo/sha context — `ExecutionTracker.getExecutionContext`. */
29
+ getExecutionContext: (runId: string) => ExecutionContext | undefined;
30
+ }
31
+ /**
32
+ * Post the terminal conclusion for a job's check run.
33
+ *
34
+ * Every job that reaches a terminal state must resolve the check run that
35
+ * `setPending()` created for it at trigger match. A check run left `queued` is
36
+ * not cosmetic: branch protection that requires it can never be satisfied, and
37
+ * a developer reads it as "still running" indefinitely with nothing to point at.
38
+ *
39
+ * A non-terminal status is a no-op — a check run is completed once, when the job
40
+ * stops.
41
+ *
42
+ * Never throws. Its callers are hot paths whose remaining work matters more than
43
+ * this report: the tracker's terminal-job hook runs inside `onJobStatus`, ahead
44
+ * of the job-complete event emission and the needs/wave scheduler hooks, and the
45
+ * expiry sweep runs it per job inside a loop. `buildJobFailureDescription` reads
46
+ * an agent-supplied payload, so a malformed `stepResults` entry is enough to
47
+ * raise — and a check-run report is not worth stalling a run's scheduling for.
48
+ */
49
+ export declare function reportJobCheckRunCompletion(deps: JobCheckRunCompletionDeps, input: JobCheckRunCompletionInput): void;
50
+ //# sourceMappingURL=job-check-run-completion.d.ts.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The single place that decides what happens to a step-log chunk.
3
+ *
4
+ * Both ingresses feed this sink: the local agent WebSocket handler and the
5
+ * coordinator's peer handler, which receives chunks relayed by a worker
6
+ * orchestrator. Keeping the policy in one place is what makes the two paths
7
+ * behave identically — a worker-dispatched job's logs are persisted, counted
8
+ * and forwarded exactly like a locally-dispatched one's.
9
+ *
10
+ * The storage key is derived from `executionTracker.getJobName`, the same call
11
+ * `ExecutionTracker.onStepStatus` uses to fill `execution_steps.log_path`, so
12
+ * the reader and the writer cannot disagree about the naming rule.
13
+ */
14
+ import type { LogStream } from '@kici-dev/engine';
15
+ import type { LogWriter } from './log-writer.js';
16
+ import type { StepLogBuffer } from './step-log-buffer.js';
17
+ /** A chunk whose lines all share one timestamp and one originating stream. */
18
+ export interface NormalizedLogChunk {
19
+ runId: string;
20
+ jobId: string;
21
+ stepIndex: number;
22
+ lines: string[];
23
+ timestamp: number;
24
+ stream?: LogStream;
25
+ }
26
+ /** Which ingress produced the chunk. Stamped onto the two log counters. */
27
+ export type LogChunkSource = 'local' | 'peer';
28
+ export interface LogChunkSinkDeps {
29
+ /** Ingress this sink instance serves; becomes the `source` metric attribute. */
30
+ source: LogChunkSource;
31
+ /** Short in-memory tail feeding the GitHub check-run summary. */
32
+ stepLogBuffer?: StepLogBuffer;
33
+ /** Durable step-log persistence. Absent when the orchestrator has no database. */
34
+ logWriter?: LogWriter;
35
+ /** Resolves the job name that names the storage path. */
36
+ executionTracker?: {
37
+ getJobName(runId: string, jobId: string): string | undefined;
38
+ };
39
+ /**
40
+ * Forward to the Platform for browser fan-out. Absent in independent mode.
41
+ * The caller wraps the chunk in the `log.chunk` envelope, which keeps this
42
+ * module free of Platform-protocol knowledge.
43
+ */
44
+ forwardToPlatform?: (chunk: NormalizedLogChunk) => void;
45
+ }
46
+ export declare function createLogChunkSink(deps: LogChunkSinkDeps): (chunk: NormalizedLogChunk) => void;
47
+ //# sourceMappingURL=log-chunk-sink.d.ts.map
@@ -0,0 +1,16 @@
1
+ import type { LogStorage } from './log-storage.js';
2
+ /**
3
+ * Delete step-log objects older than `ttlDays` in one bulk pass.
4
+ *
5
+ * Step-log objects are write-once (appended during the run, untouched after
6
+ * finalize), so last-modified time is the correct expiry basis — a recent or
7
+ * still-running run's objects were all written within the window and survive.
8
+ * `ttlDays <= 0` disables the sweep (returns 0). Expired objects are collected
9
+ * then bulk-deleted via `deleteMany` (S3 DeleteObjects), so a large sweep is a
10
+ * handful of API calls, not one-per-object. A delete failure is logged and the
11
+ * cleanup tick is not aborted.
12
+ *
13
+ * @returns Number of objects deleted.
14
+ */
15
+ export declare function pruneExpiredLogs(storage: LogStorage, ttlDays: number, now?: Date): Promise<number>;
16
+ //# sourceMappingURL=log-retention.d.ts.map
@@ -2,14 +2,33 @@
2
2
  * Log storage abstraction for the orchestrator.
3
3
  *
4
4
  * Provides a pluggable backend interface for persisting execution logs.
5
- * Unlike CacheStorage, log storage has NO TTL semantics -- logs persist
6
- * indefinitely and the customer manages their own storage lifecycle.
5
+ * Retention on the S3 backend is bounded by the orchestrator cleanup sweep
6
+ * (`KICI_STEP_LOG_TTL_DAYS`, default 90; 0 disables it); the filesystem backend
7
+ * leaves lifecycle to the operator.
7
8
  *
8
9
  * Backends: FilesystemLogStorage, S3LogStorage
9
10
  *
10
11
  * Log file layout: executions/{runId}/job-{name}/step-{index}.log
11
12
  * Format: JSONL (one JSON object per line with timestamp, level, message, metadata)
12
13
  */
14
+ /**
15
+ * Assert that a logical log path is safe to hand to a storage backend.
16
+ *
17
+ * The invariant every backend relies on: a log path is relative, contains no
18
+ * `..` segment, and carries no NUL byte. Both backends call this at their own
19
+ * boundary, so a malformed key is refused identically whichever backend the
20
+ * operator configured, and a new caller is guarded on arrival rather than by
21
+ * remembering to validate.
22
+ *
23
+ * The `..` test is per-segment, not a substring match: a job name such as
24
+ * `job-a..b` is legitimate and is accepted.
25
+ *
26
+ * `FilesystemLogStorage.fullPath()` re-states the same invariant physically,
27
+ * comparing the resolved absolute path against the resolved root. Both checks
28
+ * are lexical: neither resolves symlinks, so a symlink planted inside the log
29
+ * root still redirects a logically-clean path through it.
30
+ */
31
+ export declare function assertSafeLogPath(path: string): void;
13
32
  /**
14
33
  * Read result from LogStorage.read().
15
34
  * Supports cursor-based pagination for large log files.
@@ -34,17 +53,54 @@ export interface LogReadOptions {
34
53
  /**
35
54
  * Pluggable log storage interface.
36
55
  *
37
- * All operations are async. No TTL -- logs persist indefinitely.
56
+ * All operations are async. Retention is enforced by the cleanup sweep on the
57
+ * S3 backend (see `pruneExpiredLogs`); the filesystem backend is operator-managed.
38
58
  */
39
59
  export interface LogStorage {
40
- /** Append lines to a log file. Creates the file (and directories) if it doesn't exist. */
60
+ /**
61
+ * Append lines to a log file, immediately durable. Creates the file (and
62
+ * directories) if it doesn't exist. Used by single-shot writers (webhook
63
+ * payloads, rerun payloads, orchestration logs) that write once and expect
64
+ * the content to be readable without any subsequent finalize call.
65
+ */
41
66
  append(path: string, data: string): Promise<void>;
67
+ /**
68
+ * High-frequency append-only log write. Durability is guaranteed by a
69
+ * subsequent finalize(path) (called at run completion). S3 buffers per key
70
+ * and seals immutable segments on a size/time threshold; the filesystem
71
+ * appends immediately (appendFile is already durable).
72
+ */
73
+ appendStreaming(path: string, data: string): Promise<void>;
74
+ /**
75
+ * Seal any buffered-but-unwritten content for a path. S3 flushes its current
76
+ * tail segment; the filesystem is a no-op. Safe on any path — a no-op when
77
+ * nothing is buffered.
78
+ */
79
+ finalize(path: string): Promise<void>;
42
80
  /** Read log file content. Returns { data, cursor, complete } for pagination. */
43
81
  read(path: string, options?: LogReadOptions): Promise<LogReadResult>;
44
82
  /** Check if a log file exists. */
45
83
  exists(path: string): Promise<boolean>;
46
84
  /** List files matching a prefix (directory listing). */
47
85
  list(prefix: string): Promise<string[]>;
86
+ /**
87
+ * List every physical object under a prefix with its last-modified time,
88
+ * `path` relative to the storage prefix. Unlike {@link LogStorage.list} this
89
+ * does NOT collapse S3 segment objects (`.../seg-NNNNNN`) to their logical
90
+ * step path — the retention sweep must see and delete each physical object,
91
+ * and each segment carries its own last-modified time. Step-log objects are
92
+ * write-once, so last-modified is the correct expiry basis.
93
+ */
94
+ listWithMetadata(prefix: string): Promise<Array<{
95
+ path: string;
96
+ lastModified: Date;
97
+ }>>;
98
+ /**
99
+ * Bulk-delete objects by their storage-relative path (as returned by
100
+ * {@link LogStorage.listWithMetadata}). S3 uses `DeleteObjects` (≤1000
101
+ * keys/call, chunked); missing keys are no-ops. Returns the count deleted.
102
+ */
103
+ deleteMany(paths: string[]): Promise<number>;
48
104
  }
49
105
  /**
50
106
  * Configuration for creating a log storage backend.
@@ -61,6 +117,10 @@ type LogStorageConfig = {
61
117
  endpoint?: string;
62
118
  /** Use path-style access instead of virtual-hosted-style (required for most S3-compatible services) */
63
119
  forcePathStyle?: boolean;
120
+ /** Seal a step-log segment once the in-memory buffer reaches this many bytes. */
121
+ segmentFlushBytes: number;
122
+ /** Seal a step-log segment once the oldest buffered byte reaches this age (ms). */
123
+ segmentFlushMs: number;
64
124
  };
65
125
  /**
66
126
  * Factory function to create the appropriate LogStorage backend.
@@ -5,10 +5,14 @@
5
5
  * and metadata fields. Lines are written to the standard log path layout:
6
6
  * executions/{runId}/job-{name}/step-{index}.log
7
7
  *
8
- * For filesystem storage, data is appended immediately (no buffering).
9
- * For S3 storage, the LogStorage backend handles read-concat-put.
8
+ * Step-log writes go through the high-frequency `appendStreaming` verb: the
9
+ * filesystem backend appends immediately (no buffering); the S3 backend buffers
10
+ * per step key and seals immutable append-only segments. `drain(runId)` awaits
11
+ * the in-flight appends and then finalizes each step path so the S3 tail
12
+ * segment is sealed before a reader declares the stream complete.
10
13
  */
11
14
  import type { LogStorage } from './log-storage.js';
15
+ import { LogStream } from '@kici-dev/engine';
12
16
  import type { ObserverRegistry } from '../ws/observer-registry.js';
13
17
  interface LogWriterDeps {
14
18
  logStorage: LogStorage;
@@ -32,13 +36,22 @@ export declare class LogWriter {
32
36
  * disk, dropping it from a blocking `kici run remote` follow.
33
37
  */
34
38
  private readonly pendingAppends;
39
+ /**
40
+ * Log paths written for each run, populated in `appendChunk`. `drain(runId)`
41
+ * finalizes each one so the S3 backend seals its buffered tail segment before
42
+ * a reader declares the stream complete. The filesystem backend's `finalize`
43
+ * is a no-op.
44
+ */
45
+ private readonly runPaths;
35
46
  constructor(deps: LogWriterDeps);
36
47
  /**
37
48
  * Await every in-flight log append for `runId` that was registered before
38
- * this call. Settles even if an append rejected (errors are already logged
39
- * by `appendChunk`); the point is ordering, not error propagation. New
40
- * appends started after this call are not waited on — a terminal run emits
41
- * no further chunks, so the snapshot taken at call time is complete.
49
+ * this call, then seal each step log's buffered tail segment so the durable
50
+ * S3 record is complete. Settles even if an append rejected (errors are
51
+ * already logged by `appendChunk`); the point is ordering, not error
52
+ * propagation. New appends started after this call are not waited on — a
53
+ * terminal run emits no further chunks, so the snapshot taken at call time is
54
+ * complete.
42
55
  */
43
56
  drain(runId: string): Promise<void>;
44
57
  /**
@@ -57,8 +70,10 @@ export declare class LogWriter {
57
70
  * @param timestamp - Timestamp from the agent message (epoch ms)
58
71
  * @param jobId - Job ID for observer broadcasting (optional)
59
72
  * @param stepName - Step name for observer broadcasting (optional)
73
+ * @param stream - Which subprocess stream the lines came from. Absent means
74
+ * the agent did not report one, which is recorded as `stdout`.
60
75
  */
61
- appendChunk(runId: string, jobName: string, stepIndex: number, lines: string[], timestamp: number, jobId?: string, stepName?: string): Promise<void>;
76
+ appendChunk(runId: string, jobName: string, stepIndex: number, lines: string[], timestamp: number, jobId?: string, stepName?: string, stream?: LogStream): Promise<void>;
62
77
  }
63
78
  export {};
64
79
  //# sourceMappingURL=log-writer.d.ts.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Convert a worker-relayed `peer.log.chunk` into the chunk shape the log sink
3
+ * consumes.
4
+ *
5
+ * The peer frame carries a timestamp and an originating stream per line, while
6
+ * the sink (and `LogWriter.appendChunk` beneath it) takes one of each per
7
+ * chunk. Grouping consecutive lines that agree on both is lossless and keeps
8
+ * line order intact. A worker today gives every line in a chunk the same
9
+ * timestamp and stream, so this yields a single group in practice.
10
+ */
11
+ import type { PeerLogChunk } from '@kici-dev/engine';
12
+ import type { NormalizedLogChunk } from './log-chunk-sink.js';
13
+ export declare function normalizePeerLogChunk(chunk: PeerLogChunk): NormalizedLogChunk[];
14
+ //# sourceMappingURL=peer-log-normalize.d.ts.map