@kici-dev/orchestrator 0.8.0 → 0.9.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 (63) hide show
  1. package/dist/__test-helpers__/test-db.d.ts +21 -0
  2. package/dist/app.d.ts +6 -5
  3. package/dist/artifacts/artifact-store.d.ts +6 -10
  4. package/dist/cache/legacy-prefixes.d.ts +19 -0
  5. package/dist/cache/pending-precursor-db-watcher.d.ts +79 -0
  6. package/dist/cache/pending-tracker.d.ts +10 -0
  7. package/dist/cache/precursor-result.d.ts +68 -0
  8. package/dist/cache/source-cache.d.ts +2 -1
  9. package/dist/cache/user-cache.d.ts +37 -58
  10. package/dist/cli/api-client.d.ts +3 -6
  11. package/dist/cli/commands/cache.d.ts +51 -0
  12. package/dist/cli/commands/cluster-settings.d.ts +0 -11
  13. package/dist/cli/commands/trust-policy.d.ts +12 -42
  14. package/dist/cli/service/privilege.d.ts +27 -6
  15. package/dist/cli.js +1138 -845
  16. package/dist/cluster/cluster-settings-reader.d.ts +1 -3
  17. package/dist/cluster/coordinator.d.ts +2 -3
  18. package/dist/cluster/join-client.d.ts +1 -39
  19. package/dist/config.d.ts +0 -6
  20. package/dist/contexts/context-store.d.ts +3 -3
  21. package/dist/contexts/held-runs.d.ts +6 -6
  22. package/dist/contexts/protection/aggregate.d.ts +5 -5
  23. package/dist/dashboard/handler.d.ts +1 -1
  24. package/dist/db/migrations/142_sweep_deprecated_columns.d.ts +4 -0
  25. package/dist/db/migrations/143_execution_jobs_precursor_result.d.ts +26 -0
  26. package/dist/db/migrations/144_execution_runs_registration_window.d.ts +32 -0
  27. package/dist/db/types.d.ts +27 -13
  28. package/dist/index.js +115 -156
  29. package/dist/metrics/prometheus.d.ts +2 -2
  30. package/dist/oidc/id-token-claims.d.ts +9 -10
  31. package/dist/oidc/local-mint.d.ts +9 -12
  32. package/dist/oidc/oidc-mint-registration.d.ts +15 -28
  33. package/dist/oidc/orchestrator-mint.d.ts +10 -10
  34. package/dist/oidc/resolve-signer.d.ts +40 -0
  35. package/dist/orchestrator-core.d.ts +11 -0
  36. package/dist/pipeline/dispatch-matched-workflow.d.ts +1 -2
  37. package/dist/pipeline/job-contexts.d.ts +2 -2
  38. package/dist/provenance/attestation-retrier.d.ts +11 -1
  39. package/dist/provenance/retrier-mint.d.ts +35 -0
  40. package/dist/providers/github/normalizer.d.ts +1 -28
  41. package/dist/providers/local/normalizer.d.ts +1 -15
  42. package/dist/queue/job-queue.d.ts +2 -6
  43. package/dist/queue/terminalize-unroutable.d.ts +1 -1
  44. package/dist/reporting/execution-tracker.d.ts +107 -0
  45. package/dist/scaler/label-matcher.d.ts +0 -1
  46. package/dist/scaler/manager.d.ts +2 -24
  47. package/dist/scaler/scaler-state-store.d.ts +2 -2
  48. package/dist/security/global-workflow-policy.d.ts +3 -28
  49. package/dist/security/lock-source.d.ts +1 -1
  50. package/dist/security/trust-policy-gate.d.ts +0 -14
  51. package/dist/security/trust-policy-store.d.ts +2 -3
  52. package/dist/security/trust-resolver.d.ts +1 -2
  53. package/dist/server.js +2666 -2471
  54. package/dist/stale-detector/all-terminal-runs.d.ts +31 -0
  55. package/dist/stale-detector/stale-run-detector.d.ts +20 -0
  56. package/dist/standalone.js +2818 -2561
  57. package/dist/ws/dashboard-global-workflows-handler.d.ts +3 -4
  58. package/dist/ws/platform-client.d.ts +0 -15
  59. package/installer-image-digests.json +3 -3
  60. package/package.json +18 -17
  61. package/sbom.spdx.json +1178 -1352
  62. package/dist/ws/oidc-token-relay.d.ts +0 -79
  63. package/dist/ws/orch-rpc.d.ts +0 -9
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Teardown helper for tests that create their own PostgreSQL database.
3
+ */
4
+ import type pg from 'pg';
5
+ /**
6
+ * Terminate whatever backends still hold `dbName`, after first waiting for
7
+ * the connections the test itself closed to finish closing.
8
+ *
9
+ * `pool.end()` — and Kysely's `destroy()`, which calls it — resolves as soon
10
+ * as every client has SENT its Terminate message, not when its socket has
11
+ * closed. A `pg_terminate_backend` issued inside that window delivers a FATAL
12
+ * `57P01` to a client that is mid-shutdown and no longer carries an error
13
+ * listener, and that surfaces as an uncaught exception that fails the whole
14
+ * test file after every test in it passed. So this polls `pg_stat_activity`
15
+ * until the database has no backend left (bounded by `timeoutMs`), and
16
+ * terminates only what is still there — a connection the test never closed.
17
+ */
18
+ export declare function terminateTestDbBackends(admin: pg.Pool, dbName: string, opts?: {
19
+ timeoutMs?: number;
20
+ }): Promise<void>;
21
+ //# sourceMappingURL=test-db.d.ts.map
package/dist/app.d.ts CHANGED
@@ -146,9 +146,9 @@ export interface AppDependencies {
146
146
  */
147
147
  ingestOverflowReplayer?: IngestOverflowReplayer;
148
148
  /**
149
- * Fulfil deferred attestations on demand (mints in this process, which owns
150
- * the Platform WS). Backs `POST /api/v1/admin/attestations/retry`. Wired only
151
- * in coordinator mode where the retrier exists.
149
+ * Fulfil deferred attestations on demand (mints in this process with the
150
+ * orchestrator's own signer). Backs `POST /api/v1/admin/attestations/retry`.
151
+ * Wired only in coordinator mode where the retrier exists.
152
152
  */
153
153
  retryAttestations?: (opts: {
154
154
  runId?: string;
@@ -183,8 +183,9 @@ export interface AppDependencies {
183
183
  /**
184
184
  * In-process dev-signed identity signer for the offline local dev plane.
185
185
  * Present ONLY in independent mode with KICI_INDEPENDENT_IDENTITY=1. When set
186
- * (and NOT Platform-connected), the local mint path for `OIDC_TOKEN_REQUEST_METHOD`
187
- * is registered so `ctx.kici.oidc.token()` mints a `kici-local` dev token.
186
+ * (and no orchestrator-owned signer is configured), the local mint path for
187
+ * `OIDC_TOKEN_REQUEST_METHOD` is registered so `ctx.kici.oidc.token()` mints
188
+ * a `kici-local` dev token.
188
189
  */
189
190
  localOidcSigner?: LocalSigner;
190
191
  /**
@@ -168,12 +168,12 @@ export declare class ArtifactStore {
168
168
  * is the immutability backstop for a racing duplicate that slipped past the
169
169
  * pre-mint check.
170
170
  *
171
- * Neither agent-supplied value is trusted: the storage key is re-derived from
172
- * the server-resolved `runId` + `name`, and the recorded size is the real byte
173
- * size of the stored object read back from the storage backend. Agents run
174
- * untrusted customer workflow code, so an echoed key could otherwise point a
175
- * row at another tenant's object, and a declared size could under-account the
176
- * per-org quota (a presigned PUT does not bind content length).
171
+ * The agent asserts neither the key nor the size: the storage key is derived
172
+ * from the server-resolved `runId` + `name`, and the recorded size is the real
173
+ * byte size of the stored object read back from the storage backend. Agents
174
+ * run untrusted customer workflow code, so an echoed key could otherwise point
175
+ * a row at another tenant's object, and a declared size could under-account
176
+ * the per-org quota (a presigned PUT does not bind content length).
177
177
  *
178
178
  * The name is re-validated here, not only at `beginUpload`: a commit is its
179
179
  * own inbound message, so an agent can send one for a name it never got a
@@ -192,11 +192,7 @@ export declare class ArtifactStore {
192
192
  runId: string;
193
193
  jobId: string;
194
194
  name: string;
195
- /** Agent-declared size. Advisory — the stored object is stat'd instead. */
196
- sizeBytes: number;
197
195
  sha256: string;
198
- /** Agent-echoed key. Ignored — the key is re-derived server-side. */
199
- storageKey: string;
200
196
  }): Promise<void>;
201
197
  /** Load a single artifact row (any TTL state) for a run + name. */
202
198
  private loadRow;
@@ -0,0 +1,19 @@
1
+ /** The cache prefixes a retired layout can live under. */
2
+ export declare const LEGACY_PREFIXES: readonly ['cache/', 'source/', 'deps/'];
3
+ export type LegacyPrefix = (typeof LEGACY_PREFIXES)[number];
4
+ /**
5
+ * True only for a key in a retired layout that its shape alone identifies;
6
+ * false for every key the current writers produce.
7
+ *
8
+ * A `deps/` key is never legacy by shape (see the module comment) — use
9
+ * `legacyDepTarballKeys` over a listing for that prefix.
10
+ */
11
+ export declare function isLegacyKey(key: string): boolean;
12
+ /**
13
+ * The `deps/` tarballs in a listing that belong to the retired layout: each is
14
+ * paired with a `.hash` object under the same platform segment and stem.
15
+ */
16
+ export declare function legacyDepTarballKeys(keys: readonly string[]): string[];
17
+ /** Every legacy key in a listing, grouped by the prefix it lives under. */
18
+ export declare function classifyLegacyKeys(keys: readonly string[]): Map<LegacyPrefix, string[]>;
19
+ //# sourceMappingURL=legacy-prefixes.d.ts.map
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The shared-database channel for pending precursor jobs.
3
+ *
4
+ * The dispatch queue is cluster-wide, so a build / init / dynamic-eval job this
5
+ * coordinator dispatched may be claimed by an agent connected to a sibling
6
+ * coordinator. That agent reports to the sibling; the sibling persists the
7
+ * terminal `job.status` — status plus `execution_jobs.precursor_result` — and
8
+ * relays nothing, because between coordinators the shared row IS the report.
9
+ * The trackers on this coordinator would otherwise wait out their own timeout
10
+ * on a job the fleet already finished.
11
+ *
12
+ * While any tracker awaits a job, this watcher polls the rows for exactly that
13
+ * set and hands each terminal one to `settlePendingPrecursor` — the same
14
+ * function the local agent socket goes through, so the two channels cannot
15
+ * disagree on what "finished" means. A job the socket already settled is no
16
+ * longer tracked and is not queried again; a row that is not yet terminal
17
+ * settles nothing and is re-read on the next tick.
18
+ *
19
+ * Only builds, inits and dynamic evals ride this channel. A pre-run global
20
+ * eval round is not a run and writes no `execution_jobs` row, so it has no
21
+ * shared row to read; its own wait ceiling still bounds it.
22
+ */
23
+ import { type Kysely } from 'kysely';
24
+ import type { Database } from '../db/types.js';
25
+ import type { PendingBuildTracker } from './pending-builds.js';
26
+ import type { PendingDynamicTracker } from './pending-dynamics.js';
27
+ import type { PendingInitTracker } from './pending-inits.js';
28
+ /**
29
+ * How often the tracked rows are re-read while anything is pending.
30
+ *
31
+ * The interval is the latency a cross-coordinator build adds before the
32
+ * awaiting pipeline dispatches its real jobs, so it is short; the query is one
33
+ * primary-key lookup per tracked job and runs only while a tracker is
34
+ * non-empty, so an idle coordinator pays nothing.
35
+ */
36
+ export declare const DEFAULT_PRECURSOR_DB_POLL_MS = 2000;
37
+ export interface PendingPrecursorDbWatcherDeps {
38
+ db: Kysely<Database>;
39
+ pendingBuilds?: Pick<PendingBuildTracker, 'has' | 'resolve' | 'reject' | 'trackedJobIds'>;
40
+ pendingInits?: Pick<PendingInitTracker, 'has' | 'resolve' | 'reject' | 'trackedJobIds'>;
41
+ pendingDynamics?: Pick<PendingDynamicTracker, 'has' | 'resolve' | 'reject' | 'trackedJobIds'>;
42
+ /**
43
+ * Keeps this coordinator's in-memory job status in step with the row it
44
+ * settled from. The agent frame that would have done so went to the sibling,
45
+ * and without it the run's completion check on this coordinator reads the
46
+ * job as still pending forever.
47
+ */
48
+ executionTracker?: {
49
+ updateInMemoryJob(runId: string, jobId: string, status: string): void;
50
+ };
51
+ /** Poll interval. Defaults to {@link DEFAULT_PRECURSOR_DB_POLL_MS}. */
52
+ intervalMs?: number;
53
+ }
54
+ export declare class PendingPrecursorDbWatcher {
55
+ private readonly deps;
56
+ private readonly intervalMs;
57
+ private timer;
58
+ private tickInFlight;
59
+ /**
60
+ * Terminal rows that carried nothing the settle function could act on —
61
+ * a `success` with no precursor marker, which a coordinator that predates
62
+ * `precursor_result` writes. Logged once per job, not once per tick; the
63
+ * waiter's own timeout still bounds it.
64
+ */
65
+ private readonly warnedNoPayload;
66
+ constructor(deps: PendingPrecursorDbWatcherDeps);
67
+ start(): void;
68
+ stop(): void;
69
+ /** The job ids any tracker currently awaits, de-duplicated. */
70
+ private trackedJobIds;
71
+ /**
72
+ * One pass: read the terminal rows for every tracked job and settle them.
73
+ * Returns how many entries settled. Never throws — a read fault costs one
74
+ * tick, and the next one re-reads the same set. Single-flight, so a slow
75
+ * read cannot stack ticks.
76
+ */
77
+ tick(): Promise<number>;
78
+ }
79
+ //# sourceMappingURL=pending-precursor-db-watcher.d.ts.map
@@ -61,5 +61,15 @@ export declare class PendingTracker<T> {
61
61
  * Number of pending entries (for metrics / debugging).
62
62
  */
63
63
  get size(): number;
64
+ /**
65
+ * The job ids currently awaited, for the shared-database channel.
66
+ *
67
+ * The local agent socket is one of two channels that settle an entry. A job
68
+ * this coordinator dispatched may be claimed from the shared queue by an
69
+ * agent connected to a sibling coordinator, whose terminal frame never
70
+ * reaches this process — the sibling writes it to `execution_jobs` instead.
71
+ * `PendingPrecursorDbWatcher` polls those rows for exactly this set.
72
+ */
73
+ trackedJobIds(): string[];
64
74
  }
65
75
  //# sourceMappingURL=pending-tracker.d.ts.map
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Settling a pending precursor job — build, init, dynamic eval, global eval
3
+ * round — from a terminal `job.status`, whichever channel delivered it.
4
+ *
5
+ * Two channels feed the pending trackers:
6
+ *
7
+ * - **The local agent socket.** The agent that ran the job reports to the
8
+ * coordinator it is connected to, and `app.ts` hands the frame here.
9
+ * - **The shared database.** The dispatch queue is cluster-wide, so a job this
10
+ * coordinator dispatched may be claimed by an agent connected to a sibling
11
+ * coordinator. That sibling's frame never reaches this process; the sibling
12
+ * persists it to `execution_jobs` instead, and `PendingPrecursorDbWatcher`
13
+ * reads the row back and hands it here in the same shape.
14
+ *
15
+ * One function settles both so the two channels cannot drift on what counts as
16
+ * "the build finished" or "the init returned its result".
17
+ */
18
+ import type { LockJob } from '@kici-dev/engine';
19
+ import type { PendingBuildTracker } from './pending-builds.js';
20
+ import type { PendingDynamicTracker } from './pending-dynamics.js';
21
+ import { type PendingGlobalEvalTracker } from './pending-global-evals.js';
22
+ import type { InitResult, PendingInitTracker } from './pending-inits.js';
23
+ /**
24
+ * The part of a precursor job's terminal `job.status.data` that the awaiting
25
+ * coordinator needs, persisted to `execution_jobs.precursor_result` so a
26
+ * sibling coordinator can read it back.
27
+ *
28
+ * Only the markers and their payloads are kept. The build marker is a flag —
29
+ * the build's artifacts live in the shared source and dependency caches, keyed
30
+ * by content hash, so the waiter re-reads them from there. The init and
31
+ * dynamic-eval markers carry their results, because nothing else does.
32
+ */
33
+ export interface PrecursorResult {
34
+ buildComplete?: true;
35
+ initComplete?: true;
36
+ initResult?: InitResult;
37
+ dynamicComplete?: true;
38
+ dynamicJobs?: LockJob[];
39
+ }
40
+ /**
41
+ * Pull the precursor payload out of a terminal `job.status.data`, or `null`
42
+ * when the frame carries no precursor marker (an ordinary job).
43
+ */
44
+ export declare function extractPrecursorResult(data: Record<string, unknown> | undefined): PrecursorResult | null;
45
+ /** The settle-side surface of a tracker: membership plus the two ways to settle. */
46
+ type Settleable<T> = Pick<T, Extract<keyof T, 'has' | 'resolve' | 'reject'>>;
47
+ export interface PendingPrecursorTrackers {
48
+ pendingBuilds?: Settleable<PendingBuildTracker>;
49
+ pendingInits?: Settleable<PendingInitTracker>;
50
+ pendingDynamics?: Settleable<PendingDynamicTracker>;
51
+ pendingGlobalEvals?: Settleable<PendingGlobalEvalTracker>;
52
+ }
53
+ export interface PrecursorJobStatus {
54
+ jobId: string;
55
+ state: string;
56
+ data?: Record<string, unknown>;
57
+ }
58
+ /**
59
+ * Resolve or reject whichever tracker awaits `jobId`, from a terminal status.
60
+ *
61
+ * Returns true when an entry settled. A status for a job nobody awaits, or a
62
+ * non-terminal status, settles nothing and returns false — the caller may log
63
+ * that, but must not treat it as an error: the local channel sees every job's
64
+ * frames, precursor or not.
65
+ */
66
+ export declare function settlePendingPrecursor(trackers: PendingPrecursorTrackers, { jobId, state, data }: PrecursorJobStatus): boolean;
67
+ export {};
68
+ //# sourceMappingURL=precursor-result.d.ts.map
@@ -31,7 +31,8 @@
31
31
  * the workflow entry file alone rather than the tree the tarball holds — the
32
32
  * defect this layout closes — so serving it would serve the bug. For a v6 lock
33
33
  * the fallback is unreachable anyway, since the schema bump moves every hash.
34
- * Old objects are orphaned and aged out by the normal cache TTL.
34
+ * Old objects are orphaned. The cache TTL is enforced lazily on access, so
35
+ * nothing here removes them; `kici-admin cache purge-legacy` does.
35
36
  */
36
37
  import type { CacheStorage } from '../storage/types.js';
37
38
  /**
@@ -24,6 +24,26 @@ export interface UserCacheOrgLimits {
24
24
  }
25
25
  /** Resolves the per-org cache limits for an org id (e.g. an `org_settings` read). */
26
26
  export type UserCacheOrgLimitsReader = (orgId: string) => Promise<UserCacheOrgLimits>;
27
+ /**
28
+ * Stem prefix of an in-flight upload, `.tmp-<uuid>.tar.gz`: the presigned PUT
29
+ * target a save lands on before commit copies it to its final key. Shared with
30
+ * the retired-layout classifier so both agree on what an upload looks like.
31
+ */
32
+ export declare const TEMP_UPLOAD_STEM = ".tmp-";
33
+ /**
34
+ * Sanitize a path segment so a key can never escape its org/repo/scope
35
+ * namespace. Beyond stripping disallowed characters, a segment consisting
36
+ * only of dots (`.`, `..`, …) is replaced wholesale: such a segment is a
37
+ * dot-segment that HTTP/S3 path canonicalization collapses (`a/./b` → `a/b`,
38
+ * `a/../b` → `b`), which both corrupts the namespace and breaks the SigV4
39
+ * signature on a pre-signed PUT/GET. Repo identifiers like `.` (the internal
40
+ * provider's repo id) hit exactly this case, so the all-dots guard keeps the
41
+ * object key canonical and the namespace boundary intact.
42
+ *
43
+ * Exported so an operator tool that addresses an org's prefix from a raw org id
44
+ * (`kici-admin cache purge-legacy --org`) lands on the segment the writer used.
45
+ */
46
+ export declare function sanitizeSegment(s: string): string;
27
47
  /** Identifies the org + repo + write scope a cache operation targets. */
28
48
  export interface UserCacheRef {
29
49
  org: string;
@@ -45,6 +65,14 @@ export interface UserCacheBeginSaveResult {
45
65
  uploadUrl?: string;
46
66
  tempKey?: string;
47
67
  }
68
+ /**
69
+ * The object key a committed save under `key` lands on for this ref. This is
70
+ * the writer's own path — `beginSave` and `commitSave` address the entry
71
+ * through it — so anything that must recognise a current-layout key (the
72
+ * legacy-layout classifier tests) takes it from here rather than restating
73
+ * the format.
74
+ */
75
+ export declare function userCacheEntryKey(ref: UserCacheRef, key: string): string;
48
76
  export declare class UserCache {
49
77
  private readonly storage;
50
78
  /** Cluster-wide default quota (the `KICI_USER_CACHE_QUOTA_BYTES` value). */
@@ -69,63 +97,6 @@ export declare class UserCache {
69
97
  * restore/save because the settings lookup hiccupped.
70
98
  */
71
99
  private resolveLimits;
72
- /**
73
- * Sanitize a path segment so a key can never escape its org/repo/scope
74
- * namespace. Beyond stripping disallowed characters, a segment consisting
75
- * only of dots (`.`, `..`, …) is replaced wholesale: such a segment is a
76
- * dot-segment that HTTP/S3 path canonicalization collapses (`a/./b` → `a/b`,
77
- * `a/../b` → `b`), which both corrupts the namespace and breaks the SigV4
78
- * signature on a pre-signed PUT/GET. Repo identifiers like `.` (the internal
79
- * provider's repo id) hit exactly this case, so the all-dots guard keeps the
80
- * object key canonical and the namespace boundary intact.
81
- */
82
- private seg;
83
- /** Org-level prefix: the per-tenant isolation boundary and quota scope. */
84
- private orgPrefix;
85
- /** Org + repo prefix shared by every scope of a repo. */
86
- private repoPrefix;
87
- /** Namespace prefix for the WRITE scope of a ref (shared OR per-run isolated). */
88
- private writePrefix;
89
- /** Namespace prefixes the ref may READ, in priority order. Isolated reads its own run scope, then shared. */
90
- private readPrefixes;
91
- /**
92
- * Object key for a committed cache entry.
93
- *
94
- * The trailing discriminator is a hash of the EXACT cache key, and it is what
95
- * keeps `build` and `Build` apart on a case-insensitive namespace — the
96
- * filesystem backend on a macOS or Windows host, where the two would
97
- * otherwise resolve to one object and a restore could return the other key's
98
- * tarball.
99
- *
100
- * It is deliberately a SUFFIX: `restoreByPrefix` matches `restoreKeys` by
101
- * string prefix, so appending leaves those semantics untouched. The only
102
- * parse that has to know about it is the `matchedKey` slice.
103
- */
104
- private finalKey;
105
- /**
106
- * The pre-discriminator object key.
107
- *
108
- * @deprecated Read-only compatibility path for entries written before the key
109
- * carried a discriminator. Nothing writes this format; it is removed at the
110
- * next major, by which point every such entry has aged out of the cache TTL.
111
- */
112
- private legacyFinalKey;
113
- /**
114
- * Restore an entry still stored in the pre-discriminator key format.
115
- *
116
- * Two gates, and BOTH are load-bearing — dropping either re-creates the
117
- * wrong-cache-hit this change fixes:
118
- *
119
- * 1. `unambiguousLegacyKey` statically rules out any key that could have
120
- * collided with a case variant in the old format.
121
- * 2. The listed name must match byte-exactly. A case-insensitive backend
122
- * resolves `getUrl('build')` to an object created as `Build`, but its
123
- * *listing* reports the real created name — so comparing against the
124
- * listing is what detects that the object is not really ours.
125
- *
126
- * @deprecated Removed at the next major; see `legacyFinalKey`.
127
- */
128
- private restoreLegacyExact;
129
100
  /** Restore: try the exact key across read prefixes, then restoreKeys prefix scan (newest wins). */
130
101
  restore(ref: UserCacheRef & {
131
102
  key: string;
@@ -147,7 +118,15 @@ export declare class UserCache {
147
118
  tempKey?: string;
148
119
  }): Promise<void>;
149
120
  private readHash;
150
- /** Evict least-recently-used entries for the org until total tarball size <= the per-org quota. */
121
+ /**
122
+ * Evict least-recently-used entries for the org until total tarball size <= the per-org quota.
123
+ *
124
+ * An in-flight `.tmp-` upload is never a candidate. A presigned PUT carries
125
+ * no metadata, so it would rank oldest and be reclaimed first — freeing
126
+ * nothing, since it has no `.size` — and the concurrent commit would then
127
+ * fail on its copy to the final key. Retired un-discriminated entries stay
128
+ * in the set: they hold accounted bytes and are evictable like any other.
129
+ */
151
130
  private enforceQuota;
152
131
  }
153
132
  //# sourceMappingURL=user-cache.d.ts.map
@@ -147,13 +147,10 @@ export declare class AdminApiClient {
147
147
  timeoutSeconds?: number;
148
148
  }, outPath: string): Promise<void>;
149
149
  /**
150
- * List secret scopes for an org.
151
- *
152
- * @param allBackends - Aggregate every registered backend and return scopes
153
- * in qualified `<backend>:<path>` form. Default (false) returns the bare,
154
- * pg-only listing; that default flips at v1.0.0.
150
+ * List an org's secret scopes across every registered backend, each in
151
+ * qualified `<backend>:<path>` form.
155
152
  */
156
- listScopes(orgId: string, allBackends?: boolean): Promise<{
153
+ listScopes(orgId: string): Promise<{
157
154
  scopes: string[];
158
155
  }>;
159
156
  listKeys(orgId: string, scope: string): Promise<{
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Cache maintenance commands for kici-admin (orchestrator side).
3
+ *
4
+ * cache purge-legacy [--yes] [--org <id>] Remove objects under the retired cache layouts
5
+ *
6
+ * The CLI talks **directly** to the configured cache storage backend — the same
7
+ * `KICI_STORAGE_*` configuration the orchestrator runs with — and never through
8
+ * the orchestrator HTTP admin API: the objects it removes are ones no running
9
+ * process reads, so there is nothing for the process to mediate. No database
10
+ * and no running orchestrator are needed.
11
+ */
12
+ import type { Command } from 'commander';
13
+ import type { CacheStorage } from '../../storage/types.js';
14
+ import { type LegacyPrefix } from '../../cache/legacy-prefixes.js';
15
+ /** Build the cache storage client from the orchestrator config (S3 or filesystem). */
16
+ export declare function resolveCacheStorage(): CacheStorage;
17
+ export interface PurgeLegacyOptions {
18
+ storage: CacheStorage;
19
+ /** Delete the matched objects. Off = dry run: list and count only. */
20
+ apply: boolean;
21
+ /** Narrow the sweep to one org's user-cache prefix (`cache/<org>/`). */
22
+ org?: string;
23
+ }
24
+ export interface PurgeLegacyPrefixSummary {
25
+ count: number;
26
+ bytes: number;
27
+ }
28
+ export interface PurgeLegacySummary {
29
+ applied: boolean;
30
+ perPrefix: Map<LegacyPrefix, PurgeLegacyPrefixSummary>;
31
+ totalCount: number;
32
+ totalBytes: number;
33
+ }
34
+ /**
35
+ * Find every object under the retired cache layouts and, with `apply`, delete
36
+ * it. Sizes come from a stat of each candidate — the listing carries none —
37
+ * and a candidate that vanishes between the listing and the stat counts as
38
+ * zero bytes rather than failing the sweep.
39
+ *
40
+ * `org` limits the scan to that org's `cache/<org>/` prefix. The retired source
41
+ * and dependency layouts carry no org segment, so an org-scoped sweep leaves
42
+ * them untouched.
43
+ */
44
+ export declare function purgeLegacyCache(opts: PurgeLegacyOptions): Promise<PurgeLegacySummary>;
45
+ /** Seams the tests replace: where the storage comes from and where output goes. */
46
+ export interface CacheCommandDeps {
47
+ resolveStorage: () => CacheStorage;
48
+ out: (line: string) => void;
49
+ }
50
+ export declare function registerCacheCommands(program: Command, deps?: CacheCommandDeps): void;
51
+ //# sourceMappingURL=cache.d.ts.map
@@ -71,17 +71,6 @@ export declare function buildClusterPatch(opts: Record<string, string | undefine
71
71
  * Returns the lines rather than printing them so the check is unit-testable.
72
72
  */
73
73
  export declare function unpairedEvalTimeoutWarnings(patch: PatchBody): string[];
74
- /**
75
- * Warn for every deprecated knob the patch sets.
76
- *
77
- * The set still goes through — the column and the route field are a released
78
- * operator surface and keep accepting writes — so the warning says what the
79
- * stored value now does, which is nothing.
80
- *
81
- * Returns the lines rather than printing them so the check is unit-testable,
82
- * matching {@link unpairedEvalTimeoutWarnings}.
83
- */
84
- export declare function deprecatedKnobWarnings(patch: PatchBody): string[];
85
74
  /** Build the reset PATCH body: all knobs → null, or just the flagged ones. */
86
75
  export declare function buildClusterReset(opts: Record<string, boolean | undefined>): PatchBody;
87
76
  /**
@@ -29,25 +29,17 @@ import type { AdminApiClient } from '../api-client.js';
29
29
  /**
30
30
  * The policy shape the admin route returns.
31
31
  *
32
- * The four policy fields are OPTIONAL because a **v0.5.0 independent**
33
- * orchestrator omits them: there, and only there, no policy row and no attached
34
- * Platform meant no policy was resolved at all, and it reported
35
- * `enforcement: 'legacy'` with the fields absent. A v0.5.0 Platform-attached
36
- * orchestrator with no row still sent the fail-closed values. This build's route
37
- * always sends them in every mode, so the `unknown` fallbacks below — and the
38
- * `no policy stored` provenance wording — render only against that older
39
- * independent orchestrator.
40
- *
41
- * The route also sends a deprecated `enforcement` field. Nothing here reads it —
42
- * on this build it is always `policy` — so it is absent from this shape;
43
- * `--format json` stringifies the parsed policy object, so the field still
44
- * reaches the operator verbatim.
32
+ * The policy fields are OPTIONAL because a **v0.5.0 independent** orchestrator
33
+ * omits them: there, and only there, no policy row and no attached Platform
34
+ * meant no policy was resolved at all. A v0.5.0 Platform-attached orchestrator
35
+ * with no row still sent the fail-closed values. This build's route always sends
36
+ * them in every mode, so the `unknown` fallbacks below and the `no policy
37
+ * stored` provenance wording render only against that older independent
38
+ * orchestrator.
45
39
  */
46
40
  export interface TrustPolicyView {
47
41
  customerId: string;
48
42
  forkPolicy?: string;
49
- unknownContributorPolicy?: string;
50
- workflowChangePolicy?: string;
51
43
  approvalExpiryHours?: number;
52
44
  /**
53
45
  * The authoritative hold window. Absent from any orchestrator that predates
@@ -59,15 +51,7 @@ export interface TrustPolicyView {
59
51
  effectiveDefault?: boolean;
60
52
  platformManaged?: boolean;
61
53
  }
62
- /**
63
- * Render the policy as an aligned table, or as JSON when asked.
64
- *
65
- * The two deprecated arms are deliberately absent from the table: no dispatch
66
- * decision reads either one, so a row claiming `Unknown contributor policy:
67
- * hold` would assert an enforcement that is not happening. They are still
68
- * stored and still echoed back, and `--format json` prints the policy object the
69
- * route returned, so the values remain reachable for anyone who needs them.
70
- */
54
+ /** Render the policy as an aligned table, or as JSON when asked. */
71
55
  export declare function formatPolicy(policy: TrustPolicyView, format: string): string;
72
56
  /**
73
57
  * Warn that the fork switch in force silently drops pull requests.
@@ -78,10 +62,8 @@ export declare function formatPolicy(policy: TrustPolicyView, format: string): s
78
62
  * touched to a policy they never set, so the reader of `trust-policy show`
79
63
  * is told outright — with where to look for each individual drop.
80
64
  *
81
- * `reject` is deprecated and resolves through the same arm, so it warns too.
82
- *
83
65
  * Returns the lines rather than printing them so the check is unit-testable,
84
- * matching `policyDeprecationWarnings` above.
66
+ * matching `policyExpiryWarnings` below.
85
67
  */
86
68
  export declare function forkDropWarnings(policy: TrustPolicyView): string[];
87
69
  /**
@@ -112,21 +94,9 @@ export declare function buildPolicyPatch(opts: Record<string, string | undefined
112
94
  * one that is not applied is named rather than dropped in silence.
113
95
  *
114
96
  * Returns the lines rather than printing them so the check is unit-testable,
115
- * matching `policyDeprecationWarnings` below.
97
+ * matching `forkDropWarnings` above.
116
98
  */
117
99
  export declare function policyExpiryWarnings(patch: Record<string, string | number>): string[];
118
- /**
119
- * Warn about deprecated flags and deprecated values in an already-built patch.
120
- *
121
- * Every one of these still PATCHes through unchanged — the orchestrator stores
122
- * what it is given, and an older Platform or CLI keeps seeing the value it
123
- * expects. The warning says what the value does now, which for all three is
124
- * nothing the fork switch reads.
125
- *
126
- * Returns the lines rather than printing them so the check is unit-testable,
127
- * matching `unpairedEvalTimeoutWarnings` in `cluster-settings.ts`.
128
- */
129
- export declare function policyDeprecationWarnings(patch: Record<string, string | number>): string[];
130
100
  /** One identity link as the directory route reports it. */
131
101
  interface DirectoryIdentityLink {
132
102
  userId: string;
@@ -184,8 +154,8 @@ export declare function formatDirectoryAge(updatedAt: string, now: number): stri
184
154
  * The connected wording is a `Note:` and the disconnected one a `Warning:`,
185
155
  * because the two ask for different things: one states a property of the design
186
156
  * the reader should know, the other names a condition they should act on. The
187
- * `Warning:` producers above — `forkDropWarnings`, `policyExpiryWarnings` and
188
- * `policyDeprecationWarnings` — are all of the second kind.
157
+ * `Warning:` producers above — `forkDropWarnings` and `policyExpiryWarnings`
158
+ * are both of the second kind.
189
159
  *
190
160
  * Returns the lines rather than printing them so the check is unit-testable,
191
161
  * matching `forkDropWarnings` above.
@@ -1,15 +1,19 @@
1
1
  /**
2
2
  * Resolve the system-vs-user privilege level for a service lifecycle command.
3
3
  *
4
- * Five CLI verbs (install / start / stop / restart / uninstall) need to agree
5
- * on whether they're operating against the user-level service
6
- * (~/Library/LaunchAgents, `systemctl --user`) or the system-level one
7
- * (/Library/LaunchDaemons, `systemctl`). Default behavior (no flag) is to
8
- * auto-detect based on UID — non-root → user-level, root → system-level.
4
+ * The service lifecycle verbs (install, start, stop, restart, uninstall,
5
+ * status, logs) and `db` need to agree on whether they're operating against
6
+ * the user-level service (~/Library/LaunchAgents, `systemctl --user`) or the
7
+ * system-level one (/Library/LaunchDaemons, `systemctl`). Default behavior
8
+ * (no flag) is to auto-detect based on UID — non-root → user-level, root →
9
+ * system-level.
9
10
  *
10
11
  * Explicit `--system` / `--user-level` flags override the auto-detect.
11
12
  * They're mutually exclusive: passing both throws. Passing `--system`
12
- * without root throws with an actionable "re-run under sudo" message.
13
+ * without root throws with a `sudo` command the operator can paste: it names
14
+ * the node binary and CLI script the refused process was actually running,
15
+ * because `sudo` resets PATH to its `secure_path` and cannot find a node that
16
+ * a version manager (nvm, mise, fnm, volta) put on the operator's PATH.
13
17
  */
14
18
  export interface PrivilegeOpts {
15
19
  /** Force system-level install/lifecycle. Requires root. */
@@ -17,5 +21,22 @@ export interface PrivilegeOpts {
17
21
  /** Force user-level install/lifecycle. */
18
22
  userLevel?: boolean;
19
23
  }
24
+ /** The slice of `process` the re-run hint is built from, injectable for tests. */
25
+ export interface RerunContext {
26
+ /** `process.execPath` — the node binary actually running. */
27
+ execPath: string;
28
+ /** `process.argv` — `[node, script, ...args]`. */
29
+ argv: readonly string[];
30
+ /** `process.platform`. */
31
+ platform: NodeJS.Platform;
32
+ }
33
+ /**
34
+ * The lines appended to the `--system` refusal: a command that re-runs the
35
+ * refused invocation as root, built from the running process rather than from
36
+ * a literal `kici-admin`, which `sudo` cannot find when node comes from a
37
+ * version manager. On Windows there is no `sudo`; the hint names an elevated
38
+ * shell instead.
39
+ */
40
+ export declare function systemRerunHint(ctx: RerunContext): string;
20
41
  export declare function resolveUserLevel(opts: PrivilegeOpts): boolean;
21
42
  //# sourceMappingURL=privilege.d.ts.map