@kici-dev/orchestrator 0.1.15 → 0.1.16

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 (54) hide show
  1. package/dist/app.d.ts +25 -0
  2. package/dist/approvals/apply-decision.d.ts +60 -0
  3. package/dist/approvals/approval-resolver.d.ts +66 -0
  4. package/dist/approvals/step-approval-bridge.d.ts +72 -0
  5. package/dist/approvals/team-membership-lookup.d.ts +13 -0
  6. package/dist/cache/user-cache.d.ts +1 -1
  7. package/dist/cli/api-client.d.ts +24 -0
  8. package/dist/cli/commands/firecracker/index.d.ts +11 -0
  9. package/dist/cli/commands/firecracker/provision.d.ts +13 -0
  10. package/dist/cli/commands/firecracker/teardown.d.ts +3 -0
  11. package/dist/cli/commands/firecracker/verify.d.ts +3 -0
  12. package/dist/cli/commands/scaler.d.ts +18 -0
  13. package/dist/cli/kici-admin.d.ts +10 -1
  14. package/dist/cli/service/image-digests.d.ts +21 -0
  15. package/dist/cli.js +3692 -411
  16. package/dist/cluster/peer-client.d.ts +23 -1
  17. package/dist/cluster/peer-handler.d.ts +9 -1
  18. package/dist/db/migrations/033_org_settings_approval.d.ts +19 -0
  19. package/dist/db/migrations/034_held_runs_generalize.d.ts +24 -0
  20. package/dist/db/types.d.ts +52 -1
  21. package/dist/diagnostics/bundle-writer.d.ts +1 -17
  22. package/dist/diagnostics/checks/firecracker-network.d.ts +13 -0
  23. package/dist/diagnostics/checks/index.d.ts +2 -1
  24. package/dist/diagnostics/fleet-collector.d.ts +52 -0
  25. package/dist/diagnostics/fleet-constants.d.ts +8 -0
  26. package/dist/diagnostics/fleet-selection.d.ts +15 -0
  27. package/dist/diagnostics/fleet-topology.d.ts +47 -0
  28. package/dist/diagnostics/fleet-wiring.d.ts +60 -0
  29. package/dist/environments/held-runs.d.ts +69 -1
  30. package/dist/firecracker/host-network.d.ts +83 -0
  31. package/dist/firecracker/persist.d.ts +14 -0
  32. package/dist/index.js +24 -6
  33. package/dist/orchestrator-core.d.ts +9 -1
  34. package/dist/pipeline/processor.d.ts +22 -0
  35. package/dist/routes/fleet.d.ts +20 -0
  36. package/dist/scaler/config.d.ts +4 -2
  37. package/dist/scaler/disk-guard.d.ts +27 -0
  38. package/dist/scaler/firecracker-backend.d.ts +18 -0
  39. package/dist/scaler/manager.d.ts +6 -0
  40. package/dist/scaler/nftables.d.ts +4 -0
  41. package/dist/scaler/reap-orphans.d.ts +27 -0
  42. package/dist/scaler/types.d.ts +2 -0
  43. package/dist/server.js +3205 -1214
  44. package/dist/stale-detector/stale-run-detector.d.ts +18 -0
  45. package/dist/standalone.js +2585 -1062
  46. package/dist/storage/filesystem.d.ts +2 -1
  47. package/dist/storage/s3.d.ts +2 -1
  48. package/dist/storage/types.d.ts +7 -0
  49. package/dist/ws/agent-handler.d.ts +32 -0
  50. package/dist/ws/dashboard-env-handler.d.ts +35 -0
  51. package/dist/ws/fleet-agent-collector.d.ts +23 -0
  52. package/installer-image-digests.json +7 -0
  53. package/package.json +6 -5
  54. package/sbom.spdx.json +62 -57
package/dist/app.d.ts CHANGED
@@ -48,6 +48,8 @@ import type { PeerToPeerMessage } from '@kici-dev/engine';
48
48
  import type { AgentTokenStore } from './agent/token-store.js';
49
49
  import type { OwnershipTracker } from './agent/ownership-tracker.js';
50
50
  import type { ObserverRegistry } from './ws/observer-registry.js';
51
+ import type { FleetAgentCollector } from './ws/fleet-agent-collector.js';
52
+ import type { FleetTopology } from './diagnostics/fleet-topology.js';
51
53
  import type { TokenManager } from './secrets/token-manager.js';
52
54
  import type { SecretResolver } from './secrets/secret-resolver.js';
53
55
  import { type AdminRouteDeps } from './routes/admin.js';
@@ -68,6 +70,7 @@ import type { TrustStore } from './events/trust-store.js';
68
70
  import type { EnvironmentStore } from './environments/environment-store.js';
69
71
  import type { VariableStore } from './environments/variable-store.js';
70
72
  import type { HeldRunStore } from './environments/held-runs.js';
73
+ import type { StepApprovalBridge } from './approvals/step-approval-bridge.js';
71
74
  import type { ContributorCache } from './security/contributor-cache.js';
72
75
  import { AgentMetricsAggregator } from './metrics/agent-metrics-aggregator.js';
73
76
  /**
@@ -132,6 +135,11 @@ export interface AppDependencies {
132
135
  tokenStore?: AgentTokenStore;
133
136
  /** Job ownership tracker for validating agent messages. Optional — requires database. */
134
137
  ownershipTracker?: OwnershipTracker;
138
+ /**
139
+ * Orchestrator-scoped collector correlating fleet.logs.request with agents'
140
+ * chunked bundle responses. Shared with the fleet fan-out in orchestrator-core.
141
+ */
142
+ fleetAgentCollector?: FleetAgentCollector;
135
143
  /** Admin API route dependencies. Optional -- only mounted when secrets management is configured. */
136
144
  adminDeps?: AdminRouteDeps;
137
145
  /**
@@ -170,6 +178,8 @@ export interface AppDependencies {
170
178
  variableStore?: VariableStore;
171
179
  /** Held run store for persisting protection rule holds. Optional -- if not set, holds are not persisted. */
172
180
  heldRunStore?: HeldRunStore;
181
+ /** Step-approval bridge — opens step-scoped holds and relays their resolution back to the waiting agent. Optional. */
182
+ stepApprovalBridge?: StepApprovalBridge;
173
183
  /** Global workflow policy for org-level permission enforcement. Optional -- if not set, global workflows are dispatched without permission checks. */
174
184
  globalWorkflowPolicy?: GlobalWorkflowPolicy;
175
185
  /** Inbound webhook delivery log writer. Optional -- if not set, deliveries are not persisted to event_log. */
@@ -215,6 +225,21 @@ export interface AppDependencies {
215
225
  * can read the same store and Mimir gets agent metrics per-org.
216
226
  */
217
227
  agentMetricsAggregator?: AgentMetricsAggregator;
228
+ /**
229
+ * Fleet log-collection route backing. `getTopology` enumerates the cluster
230
+ * (no fan-out); `collectBundle` drives the recursive fan-out and returns the
231
+ * assembled ZIP. Mounted at /admin/fleet-topology + /admin/fleet-bundle.
232
+ */
233
+ fleetRoutes?: FleetRoutesDeps;
234
+ }
235
+ /** Backing for the /admin/fleet-* routes. */
236
+ export interface FleetRoutesDeps {
237
+ getTopology: () => FleetTopology;
238
+ collectBundle: (opts: {
239
+ selectors: string[];
240
+ logWindowHours?: number;
241
+ timeoutSeconds?: number;
242
+ }) => Promise<Buffer>;
218
243
  }
219
244
  /**
220
245
  * In-memory cache for step source locations extracted from lock files.
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Shared approve/reject applier — the single code path behind both the
3
+ * dashboard WS handler and the `kici` CLI HTTP route, so the authorization +
4
+ * resume story is identical regardless of the surface.
5
+ *
6
+ * Flow for an approve:
7
+ * 1. Load the hold + its recorded decisions.
8
+ * 2. Build the team-membership lookup and run `canApprove` (eligibility +
9
+ * self-approval gate). Ineligible actors are rejected with a clear reason.
10
+ * 3. Record the decision in `held_run_approvals`.
11
+ * 4. Re-evaluate. If all clauses are satisfied, `release()` the hold and
12
+ * resume the element (re-dispatch for job/workflow; the agent bridge for
13
+ * step scope — wired by the caller via `onStepRelease`).
14
+ *
15
+ * A reject records the decision and `reject()`s the hold (failing the element).
16
+ */
17
+ import { ApprovalDecision } from '@kici-dev/engine';
18
+ import type { HeldRunStore, ReleaseSignal } from '../environments/held-runs.js';
19
+ import { type TeamMembershipLookup } from './approval-resolver.js';
20
+ /** Outcome of applying a decision. */
21
+ export interface ApplyDecisionResult {
22
+ /** Whether the decision was accepted (recorded). */
23
+ accepted: boolean;
24
+ /** Human-readable status: 'released' | 'rejected' | 'pending' | 'ineligible' | 'not-found'. */
25
+ status: 'released' | 'rejected' | 'pending' | 'ineligible' | 'not-found';
26
+ /** When still pending, how many clauses remain unsatisfied. */
27
+ remainingClauses?: number;
28
+ /** When ineligible/not-found, a reason for the caller to surface. */
29
+ reason?: string;
30
+ /** The release signal, when the element was released (for the caller to resume). */
31
+ release?: ReleaseSignal;
32
+ }
33
+ /** Dependencies injected into `applyDecision` (testable in isolation). */
34
+ export interface ApplyDecisionDeps {
35
+ orgId: string;
36
+ store: HeldRunStore;
37
+ /** Team name → member user ids (from the Plan-1 trust-policy cache). */
38
+ teamMembershipLookup: TeamMembershipLookup;
39
+ /** Whether the run triggerer may self-approve (org_settings.allow_self_approval). */
40
+ allowSelfApproval: boolean;
41
+ /** Resolve the Keycloak sub of the user who triggered a run (for the self-approval gate). */
42
+ resolveTriggererSub: (runId: string) => Promise<string | undefined>;
43
+ /** Called when a job/workflow hold is released — re-dispatch the element. */
44
+ onJobRelease: (signal: ReleaseSignal) => Promise<void>;
45
+ /** Called when a step hold is released — notify the waiting agent (approved). */
46
+ onStepRelease?: (signal: ReleaseSignal) => Promise<void>;
47
+ /**
48
+ * Called when a step-scoped hold is rejected — notify the waiting agent so it
49
+ * fails the step instead of blocking until expiry. Carries the holdId.
50
+ */
51
+ onStepReject?: (heldRunId: string, reason?: string) => Promise<void> | void;
52
+ }
53
+ /** Apply a single approve/reject decision to a hold. */
54
+ export declare function applyDecision(deps: ApplyDecisionDeps, args: {
55
+ heldRunId: string;
56
+ actorSub: string;
57
+ decision: ApprovalDecision;
58
+ reason?: string;
59
+ }): Promise<ApplyDecisionResult>;
60
+ //# sourceMappingURL=apply-decision.d.ts.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Pure approval-clause evaluator. No DB access, no I/O — given a requirement,
3
+ * a team-membership lookup, the recorded decisions, and (for eligibility) the
4
+ * run's triggerer + the self-approval policy, it answers:
5
+ *
6
+ * - `evaluate` — is the requirement satisfied (all clauses ANDed), and was it
7
+ * rejected (any single reject decision)?
8
+ * - `canApprove` — may a given actor cast an approve decision right now?
9
+ *
10
+ * Both the dashboard handler and the CLI HTTP route funnel through these so the
11
+ * authorization story is identical regardless of the surface.
12
+ */
13
+ import type { ApprovalRequirement, ApproverClause } from '@kici-dev/engine';
14
+ /** A team name → set of member user ids (Keycloak subs). */
15
+ export type TeamMembershipLookup = (team: string) => Set<string>;
16
+ /** One recorded decision row (subset of `held_run_approvals`). */
17
+ export interface RecordedDecision {
18
+ approver_user_id: string;
19
+ decision: 'approve' | 'reject';
20
+ }
21
+ /** Per-clause satisfaction detail for attribution + dashboard progress. */
22
+ export interface PerClauseResult {
23
+ clause: ApproverClause;
24
+ satisfied: boolean;
25
+ /** The approver who satisfied this clause, if any. */
26
+ by?: string;
27
+ }
28
+ export interface EvaluationResult {
29
+ /** All clauses satisfied (AND). For empty clauses: any single approve. */
30
+ satisfied: boolean;
31
+ /** Any reject decision present → the element is rejected. */
32
+ rejected: boolean;
33
+ perClause: PerClauseResult[];
34
+ }
35
+ /**
36
+ * Is a single clause satisfied by any approve decision whose approver is
37
+ * eligible for the clause? Reject decisions never satisfy.
38
+ */
39
+ export declare function isClauseSatisfied(clause: ApproverClause, decisions: RecordedDecision[], lookup: TeamMembershipLookup): boolean;
40
+ /** Does an actor satisfy/qualify for a clause (team membership or exact user)? */
41
+ export declare function isActorEligibleForClause(actorSub: string, clause: ApproverClause, lookup: TeamMembershipLookup): boolean;
42
+ /**
43
+ * Evaluate a requirement against the recorded decisions.
44
+ *
45
+ * - A single reject decision sets `rejected` (the caller fails the element).
46
+ * - Empty clauses ⇒ satisfied by ANY single approve decision.
47
+ * - Non-empty clauses ⇒ satisfied iff every clause is satisfied (AND).
48
+ */
49
+ export declare function evaluate(requirement: ApprovalRequirement, decisions: RecordedDecision[], lookup: TeamMembershipLookup): EvaluationResult;
50
+ export interface CanApproveContext {
51
+ /** The Keycloak sub of the user who triggered the run. */
52
+ triggererSub: string;
53
+ /** Whether the triggerer may self-approve their own held elements. */
54
+ allowSelfApproval: boolean;
55
+ }
56
+ /**
57
+ * May `actorSub` cast an approve decision on this requirement right now?
58
+ *
59
+ * - Self-approval gate: when `allowSelfApproval` is false and the actor is the
60
+ * run's triggerer, they may never approve.
61
+ * - Eligibility: the actor must qualify for at least one still-UNSATISFIED
62
+ * clause. (Approving a clause that is already satisfied adds nothing.) For an
63
+ * empty-clause requirement, any non-triggerer-blocked actor qualifies.
64
+ */
65
+ export declare function canApprove(actorSub: string, requirement: ApprovalRequirement, decisions: RecordedDecision[], lookup: TeamMembershipLookup, ctx: CanApproveContext): boolean;
66
+ //# sourceMappingURL=approval-resolver.d.ts.map
@@ -0,0 +1,72 @@
1
+ import type { HeldRunStore } from '../environments/held-runs.js';
2
+ import type { AccessLogWriter } from '../audit/access-log.js';
3
+ /** Outcome relayed back to the waiting agent. */
4
+ export type StepApprovalOutcome = 'approved' | 'rejected' | 'expired';
5
+ /** Arguments to open a step-scoped approval hold. */
6
+ export interface StepApprovalRequest {
7
+ agentId: string;
8
+ runId: string;
9
+ jobId: string;
10
+ stepIndex: number;
11
+ stepName: string;
12
+ clauses: Array<{
13
+ team: string;
14
+ } | {
15
+ user: string;
16
+ }>;
17
+ reason: string;
18
+ /** Per-gate timeout (seconds); falls back to the org default. */
19
+ timeoutSeconds?: number;
20
+ }
21
+ /** Dependencies injected into the bridge. */
22
+ export interface StepApprovalBridgeDeps {
23
+ store: HeldRunStore;
24
+ /**
25
+ * Resolve the orchestrator's tenant org id (same value the dashboard handler
26
+ * uses to read held_runs). A function because the org is resolved post-auth
27
+ * for sourceless orchestrators.
28
+ */
29
+ resolveOrgId: () => string;
30
+ /** Resolve the authoritative approval expiry (seconds) for an org. */
31
+ resolveExpirySeconds: (orgId: string) => Promise<number>;
32
+ /**
33
+ * Access-log writer for the orchestrator audit stream. Optional -- if not
34
+ * set, the step-hold creation audit row (`held_run.request`) is skipped.
35
+ */
36
+ accessLogWriter?: AccessLogWriter;
37
+ /** Routing key recorded on the audit row. Optional. */
38
+ routingKey?: string | null;
39
+ }
40
+ /**
41
+ * Owns the step-scoped held_runs rows and the map from holdId → the waiting
42
+ * agent's resolver. The only place that opens a step hold and the only place
43
+ * that settles a step-scoped wait.
44
+ */
45
+ export declare class StepApprovalBridge {
46
+ private readonly deps;
47
+ private readonly pending;
48
+ constructor(deps: StepApprovalBridgeDeps);
49
+ /**
50
+ * Create a step-scoped hold and return a promise that settles when the hold
51
+ * is approved / rejected / expired (via {@link resolve}).
52
+ */
53
+ request(req: StepApprovalRequest): Promise<{
54
+ outcome: StepApprovalOutcome;
55
+ reason?: string;
56
+ }>;
57
+ /**
58
+ * Settle a step-scoped wait. Called by the approve/reject applier
59
+ * (`onStepRelease`) and by the stale detector (`expired`). A no-op when no
60
+ * agent is waiting on the hold (e.g. the agent already disconnected).
61
+ */
62
+ resolve(holdId: string, outcome: StepApprovalOutcome, reason?: string): boolean;
63
+ /**
64
+ * Reject every pending wait for a disconnected agent. The held_runs rows are
65
+ * left as-is (the stale detector will expire them) — only the in-memory
66
+ * resolver is dropped so the handler's relayed `ws.send` is skipped.
67
+ */
68
+ failAgent(agentId: string): void;
69
+ /** Number of in-flight step-approval waits (test/diagnostics). */
70
+ size(): number;
71
+ }
72
+ //# sourceMappingURL=step-approval-bridge.d.ts.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Read-only lookup over the team memberships pushed from the Platform via
3
+ * `trust_policy.update`. The orchestrator has no identity store, so this is the
4
+ * only source of "who is in team X" — the approval resolver uses it to satisfy
5
+ * `{team}` approver clauses.
6
+ */
7
+ export interface TeamMembershipLookup {
8
+ /** Returns the set of member user ids for a team name (empty if unknown). */
9
+ getTeamMembers(name: string): Set<string>;
10
+ }
11
+ /** A lookup backed by no teams — the default before any push arrives. */
12
+ export declare const EMPTY_TEAM_MEMBERSHIP_LOOKUP: TeamMembershipLookup;
13
+ //# sourceMappingURL=team-membership-lookup.d.ts.map
@@ -110,7 +110,7 @@ export declare class UserCache {
110
110
  tempKey?: string;
111
111
  }): Promise<void>;
112
112
  private readHash;
113
- /** Evict oldest entries for the org until total tarball size <= the per-org quota. */
113
+ /** Evict least-recently-used entries for the org until total tarball size <= the per-org quota. */
114
114
  private enforceQuota;
115
115
  }
116
116
  //# sourceMappingURL=user-cache.d.ts.map
@@ -5,6 +5,19 @@
5
5
  * All methods are thin wrappers around fetch() that handle JSON serialization,
6
6
  * error formatting, and URL construction.
7
7
  */
8
+ /** A node in the fleet topology returned by GET /admin/fleet-topology. */
9
+ export interface FleetTopologyNodeResponse {
10
+ kind: 'orchestrator' | 'agent';
11
+ id: string;
12
+ role?: 'coordinator' | 'worker';
13
+ hostname?: string;
14
+ labels: Record<string, string>;
15
+ parentId: string | null;
16
+ }
17
+ /** Response shape for GET /admin/fleet-topology. */
18
+ export interface FleetTopologyResponse {
19
+ nodes: FleetTopologyNodeResponse[];
20
+ }
8
21
  /**
9
22
  * Universal-git configuration shape carried in `git_config` on
10
23
  * generic_webhook_sources. Matches `UniversalGitConfigSchema` in
@@ -71,6 +84,17 @@ export declare class AdminApiClient {
71
84
  * Public GET request returning raw response text.
72
85
  */
73
86
  getText(path: string): Promise<string>;
87
+ /** Enumerate the cluster topology for `debug-bundle --fleet --list` / `--pick`. */
88
+ getFleetTopology(): Promise<FleetTopologyResponse>;
89
+ /**
90
+ * Drive the fleet fan-out and write the assembled ZIP to `outPath`. The
91
+ * response is an octet-stream, so it is read as bytes rather than parsed JSON.
92
+ */
93
+ downloadFleetBundle(body: {
94
+ selectors: string[];
95
+ logWindowHours?: number;
96
+ timeoutSeconds?: number;
97
+ }, outPath: string): Promise<void>;
74
98
  listScopes(orgId: string): Promise<{
75
99
  scopes: string[];
76
100
  }>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `kici-admin firecracker` command group.
3
+ *
4
+ * Local host-networking ops for the orchestrator's Firecracker scaler:
5
+ * provision/verify/teardown the per-coordinator bridge + nft table, with
6
+ * --persist for reboot survival. These run directly on the host (privileged
7
+ * `ip`/`nft`/`systemctl`) and do NOT use the admin HTTP client.
8
+ */
9
+ import type { Command } from 'commander';
10
+ export declare function registerFirecrackerCommands(program: Command): void;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,13 @@
1
+ import type { Command } from 'commander';
2
+ import { type FirecrackerBridgeConfig } from '../../../firecracker/host-network.js';
3
+ export interface ProvisionOptions {
4
+ bridge: string;
5
+ cidr?: string;
6
+ table?: string;
7
+ hostIface?: string;
8
+ persist?: boolean;
9
+ sudo?: boolean;
10
+ }
11
+ export declare function optionsToConfig(opts: ProvisionOptions): FirecrackerBridgeConfig;
12
+ export declare function registerProvision(group: Command): void;
13
+ //# sourceMappingURL=provision.d.ts.map
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerTeardown(group: Command): void;
3
+ //# sourceMappingURL=teardown.d.ts.map
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerVerify(group: Command): void;
3
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Standalone scaler maintenance commands for kici-admin.
3
+ *
4
+ * scaler reap-orphans Free leaked Firecracker/container resources WITHOUT a
5
+ * running orchestrator (recovery for a wedged node whose
6
+ * data disk is full).
7
+ *
8
+ * This command loads the orchestrator's LOCAL config (no HTTP admin API, no DB)
9
+ * and runs the liveness-driven reaper. It is the sanctioned operator path for
10
+ * the ENOSPC bootstrap deadlock: when the data disk is 100% full the
11
+ * orchestrator crash-loops before its in-process orphan sweep can run, so a
12
+ * tool that frees disk WITHOUT the orchestrator is required.
13
+ */
14
+ import type { Command } from 'commander';
15
+ /** Probe the local orchestrator /health endpoint. Healthy => it reaps itself. */
16
+ export declare function isOrchestratorHealthy(port: number, basePath: string): Promise<boolean>;
17
+ export declare function registerScalerCommands(program: Command): void;
18
+ //# sourceMappingURL=scaler.d.ts.map
@@ -9,5 +9,14 @@
9
9
  * --url / -u Orchestrator URL (env: KICI_ADMIN_URL, default: http://localhost:8080)
10
10
  * --token / -t Admin API token (env: KICI_ADMIN_TOKEN, required)
11
11
  */
12
- export {};
12
+ import { Command } from 'commander';
13
+ /**
14
+ * Build the kici-admin Commander program with every command group registered.
15
+ * Exported so the surface registry can walk the real command tree without
16
+ * parsing argv (no action runs during a tree walk, so `getClient` is
17
+ * constructed but never invoked).
18
+ */
19
+ export declare function buildProgram(): Command;
20
+ /** Build the program and parse argv — the bin-shim entry point. */
21
+ export declare function runCli(argv?: string[]): void;
13
22
  //# sourceMappingURL=kici-admin.d.ts.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Resolves the customer installer's image reference from the digest record
3
+ * shipped with this package (installer-image-digests.json at the package root).
4
+ *
5
+ * Emits a manifest-list-digest-pinned ref so a `docker`/`podman pull` verifies
6
+ * the image hash — a registry that serves substituted bits fails the pull. When
7
+ * the record is missing (a dev tree that never ran a release) or lacks the
8
+ * image, falls back to the moving `:latest` tag and warns.
9
+ *
10
+ * The record resolves relative to this module's directory and works for both
11
+ * layouts: the source tree (`src/cli/service/` → the JSON is three dirs up at
12
+ * the package root) and the single-file bundle (`dist/cli.js` → one dir up).
13
+ */
14
+ /**
15
+ * Resolve `quay.io/kici-dev/<name>:<version>@sha256:<digest>` for an installer
16
+ * image. Falls back to `:latest` (with a warning) when no recorded digest exists.
17
+ */
18
+ export declare function resolveImageRef(name: string, opts?: {
19
+ filePath?: string;
20
+ }): string;
21
+ //# sourceMappingURL=image-digests.d.ts.map