@kici-dev/orchestrator 0.7.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.
- package/dist/__test-helpers__/test-db.d.ts +21 -0
- package/dist/app.d.ts +6 -5
- package/dist/artifacts/artifact-store.d.ts +6 -10
- package/dist/cache/legacy-prefixes.d.ts +19 -0
- package/dist/cache/pending-precursor-db-watcher.d.ts +79 -0
- package/dist/cache/pending-tracker.d.ts +10 -0
- package/dist/cache/precursor-result.d.ts +68 -0
- package/dist/cache/source-cache.d.ts +2 -1
- package/dist/cache/user-cache.d.ts +37 -58
- package/dist/cli/api-client.d.ts +3 -6
- package/dist/cli/commands/cache.d.ts +51 -0
- package/dist/cli/commands/cluster-settings.d.ts +0 -11
- package/dist/cli/commands/trust-policy.d.ts +12 -42
- package/dist/cli/service/privilege.d.ts +27 -6
- package/dist/cli.js +1171 -860
- package/dist/cluster/cluster-settings-reader.d.ts +1 -3
- package/dist/cluster/coordinator.d.ts +2 -3
- package/dist/cluster/join-client.d.ts +1 -39
- package/dist/config.d.ts +0 -6
- package/dist/contexts/context-store.d.ts +3 -3
- package/dist/contexts/held-runs.d.ts +6 -6
- package/dist/contexts/protection/aggregate.d.ts +5 -5
- package/dist/dashboard/handler.d.ts +1 -1
- package/dist/db/migrations/142_sweep_deprecated_columns.d.ts +4 -0
- package/dist/db/migrations/143_execution_jobs_precursor_result.d.ts +26 -0
- package/dist/db/migrations/144_execution_runs_registration_window.d.ts +32 -0
- package/dist/db/types.d.ts +27 -13
- package/dist/index.js +115 -156
- package/dist/metrics/prometheus.d.ts +2 -2
- package/dist/oidc/id-token-claims.d.ts +9 -10
- package/dist/oidc/local-mint.d.ts +9 -12
- package/dist/oidc/oidc-mint-registration.d.ts +15 -28
- package/dist/oidc/orchestrator-mint.d.ts +10 -10
- package/dist/oidc/resolve-signer.d.ts +40 -0
- package/dist/orchestrator-core.d.ts +11 -0
- package/dist/pipeline/dispatch-matched-workflow.d.ts +1 -2
- package/dist/pipeline/job-contexts.d.ts +2 -2
- package/dist/provenance/attestation-retrier.d.ts +11 -1
- package/dist/provenance/retrier-mint.d.ts +35 -0
- package/dist/providers/github/normalizer.d.ts +1 -28
- package/dist/providers/local/lock-file-fetcher.d.ts +8 -0
- package/dist/providers/local/normalizer.d.ts +1 -15
- package/dist/queue/job-queue.d.ts +2 -6
- package/dist/queue/terminalize-unroutable.d.ts +1 -1
- package/dist/reporting/execution-tracker.d.ts +107 -0
- package/dist/scaler/claim-store.d.ts +0 -2
- package/dist/scaler/event-backend.d.ts +7 -6
- package/dist/scaler/label-matcher.d.ts +0 -1
- package/dist/scaler/manager.d.ts +20 -26
- package/dist/scaler/scaler-state-store.d.ts +9 -2
- package/dist/scaler/types.d.ts +10 -0
- package/dist/scaler/warm-pool.d.ts +6 -4
- package/dist/security/global-workflow-policy.d.ts +3 -28
- package/dist/security/identity-link.d.ts +1 -1
- package/dist/security/lock-source.d.ts +1 -1
- package/dist/security/trust-directory-store.d.ts +1 -1
- package/dist/security/trust-policy-gate.d.ts +0 -14
- package/dist/security/trust-policy-store.d.ts +2 -3
- package/dist/security/trust-resolver.d.ts +1 -2
- package/dist/server.js +2810 -2524
- package/dist/stale-detector/all-terminal-runs.d.ts +31 -0
- package/dist/stale-detector/stale-run-detector.d.ts +20 -0
- package/dist/standalone.js +2956 -2610
- package/dist/webhook/ingest-overflow-replayer.d.ts +1 -1
- package/dist/webhook/relay-reinject.d.ts +1 -1
- package/dist/worker/in-memory-job-queue.d.ts +9 -0
- package/dist/ws/dashboard-global-workflows-handler.d.ts +3 -4
- package/dist/ws/fleet-agent-collector.d.ts +7 -0
- package/dist/ws/platform-client.d.ts +0 -15
- package/dist/ws/test-relay-handlers.d.ts +2 -0
- package/installer-image-digests.json +3 -3
- package/package.json +18 -17
- package/sbom.spdx.json +1178 -1352
- package/dist/ws/oidc-token-relay.d.ts +0 -79
- 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
|
|
150
|
-
*
|
|
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
|
|
187
|
-
* is registered so `ctx.kici.oidc.token()` mints
|
|
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
|
-
*
|
|
172
|
-
* the server-resolved `runId` + `name`, and the recorded size is the real
|
|
173
|
-
* size of the stored object read back from the storage backend. Agents
|
|
174
|
-
* untrusted customer workflow code, so an echoed key could otherwise point
|
|
175
|
-
* row at another tenant's object, and a declared size could under-account
|
|
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
|
|
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
|
-
/**
|
|
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
|
package/dist/cli/api-client.d.ts
CHANGED
|
@@ -147,13 +147,10 @@ export declare class AdminApiClient {
|
|
|
147
147
|
timeoutSeconds?: number;
|
|
148
148
|
}, outPath: string): Promise<void>;
|
|
149
149
|
/**
|
|
150
|
-
* List secret scopes
|
|
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
|
|
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
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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 `
|
|
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 `
|
|
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
|
|
188
|
-
*
|
|
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
|
-
*
|
|
5
|
-
* on whether they're operating against
|
|
6
|
-
* (~/Library/LaunchAgents, `systemctl --user`) or the
|
|
7
|
-
* (/Library/LaunchDaemons, `systemctl`). Default behavior
|
|
8
|
-
* auto-detect based on UID — non-root → user-level, root →
|
|
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
|
|
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
|