@kici-dev/agent 0.1.16 → 0.1.17
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/execution/dep-installer.d.ts +7 -3
- package/dist/execution/dep-packer.d.ts +6 -1
- package/dist/execution/env-init/presets/directives.d.ts +20 -0
- package/dist/execution/env-init/presets/expand.d.ts +16 -0
- package/dist/execution/env-init/presets/mise/cache-key.d.ts +7 -0
- package/dist/execution/env-init/presets/mise/expander.d.ts +16 -0
- package/dist/execution/env-init/presets/mise/templates.d.ts +16 -0
- package/dist/execution/env-init/presets/mise/windows-install.d.ts +18 -0
- package/dist/execution/env-init/presets/registry.d.ts +31 -0
- package/dist/execution/init-runner.d.ts +7 -0
- package/dist/execution/job-runner.d.ts +9 -1
- package/dist/execution/sandbox/env-delta.d.ts +2 -0
- package/dist/execution/sandbox/index.d.ts +1 -1
- package/dist/execution/sandbox/ipc-protocol.d.ts +58 -3
- package/dist/execution/sandbox/types.d.ts +9 -1
- package/dist/execution/sandbox/workflow-runner.d.ts +12 -7
- package/dist/execution/validate-kici-deps.d.ts +5 -0
- package/dist/execution/workspace-siblings.d.ts +33 -0
- package/dist/index.js +223 -6
- package/dist/provenance/attest.d.ts +30 -0
- package/dist/provenance/sign.d.ts +21 -0
- package/dist/provenance/statement-builder.d.ts +38 -0
- package/dist/server.js +472 -125
- package/dist/version.d.ts +2 -0
- package/dist/workflow-runner.js +749 -44
- package/dist/ws/orchestrator-client.d.ts +16 -1
- package/package.json +6 -5
- package/sbom.spdx.json +95 -65
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
import { LOGGER_ENV_VARS, defineEnv, validateUnknownKiciVars } from "@kici-dev/shared/env";
|
|
6
6
|
import { KNOWN_ROLES, validateNoReservedLabels } from "@kici-dev/engine";
|
|
7
7
|
import { execFile } from "node:child_process";
|
|
8
|
-
import { access, cp, mkdir, mkdtemp, readFile, readdir, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
8
|
+
import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
9
9
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
import { createLogger, sha256, toErrorMessage } from "@kici-dev/shared";
|
|
@@ -309,6 +309,11 @@ function redactNpmOutput(input, tokens) {
|
|
|
309
309
|
* clones the whole repo, so an in-repo sibling is present), and resolves
|
|
310
310
|
* `file:`/`link:`/`portal:` against a path — allowed when that path stays
|
|
311
311
|
* inside the cloned repo, rejected when it escapes the clone.
|
|
312
|
+
* - yarn classic (v1) has no `workspace:` protocol and no `portal:` — it links
|
|
313
|
+
* in-repo siblings by version range, not by a local specifier — so both are
|
|
314
|
+
* rejected with guidance; `file:`/`link:` are allowed when the path stays
|
|
315
|
+
* inside the clone, rejected when it escapes. (yarn berry is not yet
|
|
316
|
+
* supported.)
|
|
312
317
|
*
|
|
313
318
|
* This module performs that classification so unresolvable specifiers fail
|
|
314
319
|
* fast with guidance rather than a cryptic install error.
|
|
@@ -405,6 +410,17 @@ function isInsideRepo(repoRoot, target) {
|
|
|
405
410
|
*/
|
|
406
411
|
async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot) {
|
|
407
412
|
if (packageManager === PackageManager.Npm) return [...deps];
|
|
413
|
+
if (packageManager === PackageManager.Yarn) {
|
|
414
|
+
const unresolvable = [];
|
|
415
|
+
for (const dep of deps) {
|
|
416
|
+
if (dep.protocol === "workspace:" || dep.protocol === "portal:") {
|
|
417
|
+
unresolvable.push(dep);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (!isInsideRepo(repoRoot, resolveLocalPath(kiciDir, dep))) unresolvable.push(dep);
|
|
421
|
+
}
|
|
422
|
+
return unresolvable;
|
|
423
|
+
}
|
|
408
424
|
const hasWorkspaceFile = await fileExists(join(repoRoot, "pnpm-workspace.yaml"));
|
|
409
425
|
const unresolvable = [];
|
|
410
426
|
for (const dep of deps) {
|
|
@@ -420,6 +436,7 @@ async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot) {
|
|
|
420
436
|
function formatUnresolvableDepError(offenders, packageManager) {
|
|
421
437
|
const list = offenders.map((o) => `${o.name}: ${o.spec}`).join(", ");
|
|
422
438
|
if (packageManager === PackageManager.Npm) return `These .kici/ dependencies use local-protocol specifiers npm cannot resolve from a registry: ${list}. npm has no workspace protocol — pin a published version, publish the package to your registry, or use pnpm so an in-repo workspace sibling can be resolved.`;
|
|
439
|
+
if (packageManager === PackageManager.Yarn) return `These .kici/ dependencies use specifiers yarn classic cannot resolve: ${list}. yarn classic has no workspace: or portal: protocol — reference an in-repo sibling by a version range (yarn links matching workspace members), use pnpm, or keep file:/link: paths inside this repository. (yarn berry support is planned.)`;
|
|
423
440
|
return `These .kici/ dependencies point outside the cloned repository, which the agent never has: ${list}. A workspace: dependency requires a pnpm-workspace.yaml at the repo root, and file:/link:/portal: paths must stay inside this repository.`;
|
|
424
441
|
}
|
|
425
442
|
/**
|
|
@@ -438,6 +455,101 @@ async function assertResolvableDeps(args) {
|
|
|
438
455
|
throw new Error(formatUnresolvableDepError(offenders, args.packageManager));
|
|
439
456
|
}
|
|
440
457
|
//#endregion
|
|
458
|
+
//#region src/execution/workspace-siblings.ts
|
|
459
|
+
/**
|
|
460
|
+
* In-repo workspace-sibling discovery for the agent's dependency handling.
|
|
461
|
+
*
|
|
462
|
+
* A pnpm or yarn-classic workspace lays out a `.kici/` member's `workspace:`
|
|
463
|
+
* (pnpm) or version-range (yarn) siblings as symlinks pointing at package
|
|
464
|
+
* directories that live inside the clone but outside `.kici/` and outside the
|
|
465
|
+
* `node_modules` store. The dep-cache packer must travel those sibling dirs with
|
|
466
|
+
* the closure (their symlinks would dangle otherwise), and the yarn install path
|
|
467
|
+
* must build them (the install links a sibling but does not build it).
|
|
468
|
+
*
|
|
469
|
+
* `collectInRepoSiblings` walks a starting `node_modules` (and transitively each
|
|
470
|
+
* discovered sibling's `node_modules`), returning each in-repo sibling directory
|
|
471
|
+
* once, repo-root-relative, in breadth-first discovery order. The starting
|
|
472
|
+
* `node_modules` is a parameter so it serves pnpm + yarn-standalone (seeded at
|
|
473
|
+
* `.kici/node_modules`) and yarn-workspace-member (seeded at the hoisted root
|
|
474
|
+
* `node_modules`).
|
|
475
|
+
*/
|
|
476
|
+
/**
|
|
477
|
+
* The directory yarn lays `.kici`'s dependencies into. A standalone `.kici`
|
|
478
|
+
* (own lockfile, no parent workspace) gets `.kici/node_modules`; a workspace
|
|
479
|
+
* member hoists everything to the repo-root `node_modules`, leaving no
|
|
480
|
+
* `.kici/node_modules`.
|
|
481
|
+
*/
|
|
482
|
+
function resolveYarnNodeModulesRoot(repoRoot, kiciDir) {
|
|
483
|
+
const kiciNm = join(kiciDir, "node_modules");
|
|
484
|
+
return existsSync(kiciNm) ? kiciNm : join(repoRoot, "node_modules");
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Walk `seedNodeModules` (and transitively each in-repo sibling's
|
|
488
|
+
* `node_modules`) collecting the repo-root-relative directories of workspace
|
|
489
|
+
* siblings — package dirs that live inside the clone but outside `.kici/` and
|
|
490
|
+
* outside the repo-root `node_modules/` store. Returns each dir once, in
|
|
491
|
+
* discovery (BFS) order.
|
|
492
|
+
*/
|
|
493
|
+
async function collectInRepoSiblings(workDir, kiciDir, seedNodeModules = join(kiciDir, "node_modules")) {
|
|
494
|
+
const repoRoot = resolve(workDir);
|
|
495
|
+
const kiciResolved = resolve(kiciDir);
|
|
496
|
+
const rootNodeModules = resolve(join(workDir, "node_modules"));
|
|
497
|
+
const found = /* @__PURE__ */ new Set();
|
|
498
|
+
const visited = /* @__PURE__ */ new Set();
|
|
499
|
+
const queue = [seedNodeModules];
|
|
500
|
+
while (queue.length > 0) {
|
|
501
|
+
const nmDir = queue.shift();
|
|
502
|
+
const real = await realpath(nmDir).catch(() => null);
|
|
503
|
+
if (!real || visited.has(real)) continue;
|
|
504
|
+
visited.add(real);
|
|
505
|
+
for (const target of await resolveNodeModulesLinks(nmDir)) {
|
|
506
|
+
if (!isInside(repoRoot, target)) continue;
|
|
507
|
+
if (isInside(kiciResolved, target) || isInside(rootNodeModules, target)) continue;
|
|
508
|
+
const rel = relative(workDir, target);
|
|
509
|
+
if (!found.has(rel)) {
|
|
510
|
+
found.add(rel);
|
|
511
|
+
queue.push(join(target, "node_modules"));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return [...found];
|
|
516
|
+
}
|
|
517
|
+
/** Resolve every package symlink target under a `node_modules` dir (descending one level into `@scope` dirs). */
|
|
518
|
+
async function resolveNodeModulesLinks(nmDir) {
|
|
519
|
+
const targets = [];
|
|
520
|
+
for (const entry of await readdir(nmDir).catch(() => [])) {
|
|
521
|
+
if (entry.startsWith(".")) continue;
|
|
522
|
+
const entryPath = join(nmDir, entry);
|
|
523
|
+
if (entry.startsWith("@")) {
|
|
524
|
+
for (const scoped of await readdir(entryPath).catch(() => [])) {
|
|
525
|
+
const target = await resolveIfSymlink(join(entryPath, scoped));
|
|
526
|
+
if (target) targets.push(target);
|
|
527
|
+
}
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
const target = await resolveIfSymlink(entryPath);
|
|
531
|
+
if (target) targets.push(target);
|
|
532
|
+
}
|
|
533
|
+
return targets;
|
|
534
|
+
}
|
|
535
|
+
/** Return the real path of `p` if it is a symlink, else null. */
|
|
536
|
+
async function resolveIfSymlink(p) {
|
|
537
|
+
try {
|
|
538
|
+
if (!(await lstat(p)).isSymbolicLink()) return null;
|
|
539
|
+
return await realpath(p);
|
|
540
|
+
} catch {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/** Whether `target` is `root` itself or a path inside it. */
|
|
545
|
+
function isInside(root, target) {
|
|
546
|
+
const rel = relative(root, target);
|
|
547
|
+
return rel === "" || !rel.startsWith("..") && !rel.startsWith(`..${sep}`) && !isAbsoluteRel(rel);
|
|
548
|
+
}
|
|
549
|
+
function isAbsoluteRel(rel) {
|
|
550
|
+
return rel.length > 1 && rel[1] === ":";
|
|
551
|
+
}
|
|
552
|
+
//#endregion
|
|
441
553
|
//#region src/execution/dep-installer.ts
|
|
442
554
|
/**
|
|
443
555
|
* Inline dependency installation for graceful degradation.
|
|
@@ -445,12 +557,14 @@ async function assertResolvableDeps(args) {
|
|
|
445
557
|
* When the dep cache is unavailable or a download fails, the agent installs
|
|
446
558
|
* `.kici/` dependencies directly with the repository's package manager.
|
|
447
559
|
*
|
|
448
|
-
* The package manager is detected from the cloned repo (npm / pnpm); the
|
|
560
|
+
* The package manager is detected from the cloned repo (npm / pnpm / yarn); the
|
|
449
561
|
* presence of `.kici/package.json` signals that deps should be installed. npm
|
|
450
562
|
* is the default and ships with every Node.js install; pnpm is used when the
|
|
451
563
|
* repo is a pnpm workspace so a `.kici/` member can resolve in-repo
|
|
452
|
-
* `workspace:` siblings. yarn
|
|
453
|
-
*
|
|
564
|
+
* `workspace:` siblings. yarn classic (v1) is supported for registry
|
|
565
|
+
* dependencies and version-range workspace siblings (which it links but does
|
|
566
|
+
* not build, so the agent builds the in-repo closure after install). yarn
|
|
567
|
+
* berry (v2+) is not yet supported.
|
|
454
568
|
*
|
|
455
569
|
* Security: the install runs with an isolated per-invocation cache/store
|
|
456
570
|
* directory to prevent cache poisoning across build jobs — a malicious
|
|
@@ -499,7 +613,6 @@ async function installDeps(kiciDir, opts = {}) {
|
|
|
499
613
|
dir: kiciDir
|
|
500
614
|
});
|
|
501
615
|
process.stderr.write(`[dep-installer:trace] starting install: pm=${packageManager}, cwd=${kiciDir}\n`);
|
|
502
|
-
if (packageManager === PackageManager.Yarn) throw new Error("This repository uses yarn, which the KiCI agent does not yet support for .kici/ dependency installation. Use npm or pnpm for the .kici/ project, or open a feature request for yarn support.");
|
|
503
616
|
await assertResolvableDeps({
|
|
504
617
|
kiciDir,
|
|
505
618
|
repoRoot,
|
|
@@ -519,6 +632,11 @@ async function installDeps(kiciDir, opts = {}) {
|
|
|
519
632
|
hasPrivateRegistry,
|
|
520
633
|
registryConfig
|
|
521
634
|
});
|
|
635
|
+
else if (packageManager === PackageManager.Yarn) await runYarnInstall({
|
|
636
|
+
kiciDir,
|
|
637
|
+
hasPrivateRegistry,
|
|
638
|
+
registryConfig
|
|
639
|
+
});
|
|
522
640
|
else await runNpmInstall({
|
|
523
641
|
kiciDir,
|
|
524
642
|
hasPrivateRegistry,
|
|
@@ -533,6 +651,7 @@ async function installDeps(kiciDir, opts = {}) {
|
|
|
533
651
|
await registryConfig.cleanup();
|
|
534
652
|
}
|
|
535
653
|
if (packageManager === PackageManager.Pnpm && await kiciHasLocalProtocolDeps(kiciDir)) await buildWorkspaceClosure(repoRoot);
|
|
654
|
+
if (packageManager === PackageManager.Yarn) await buildYarnWorkspaceClosure(repoRoot, kiciDir);
|
|
536
655
|
const durationMs = Date.now() - startTime;
|
|
537
656
|
process.stderr.write(`[dep-installer:trace] install complete: ${durationMs}ms\n`);
|
|
538
657
|
logger$2.info("Deps installed inline", {
|
|
@@ -620,6 +739,101 @@ async function runPnpmInstall(args) {
|
|
|
620
739
|
}).catch(() => {});
|
|
621
740
|
}
|
|
622
741
|
}
|
|
742
|
+
/** Pure: argv for `yarn install` with an isolated cache folder. */
|
|
743
|
+
function buildYarnInstallArgs(cacheDir, hasPrivateRegistry) {
|
|
744
|
+
const a = [
|
|
745
|
+
"install",
|
|
746
|
+
"--cache-folder",
|
|
747
|
+
cacheDir,
|
|
748
|
+
"--non-interactive",
|
|
749
|
+
"--no-progress"
|
|
750
|
+
];
|
|
751
|
+
if (hasPrivateRegistry) a.push("--ignore-scripts");
|
|
752
|
+
return a;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Run `yarn install` from `.kici/` with an isolated cache folder. yarn classic
|
|
756
|
+
* reads the synthesized `.kici/.npmrc` (registry + `${VAR}` token expansion) for
|
|
757
|
+
* private-registry auth. A workspace member hoists deps to the repo-root
|
|
758
|
+
* node_modules; a standalone `.kici` gets `.kici/node_modules`. Not
|
|
759
|
+
* `--frozen-lockfile` (resolved URLs in the lockfile may point at a different
|
|
760
|
+
* registry than the synthesized `.npmrc`, e.g. localhost tunnel vs direct IP).
|
|
761
|
+
*/
|
|
762
|
+
async function runYarnInstall(args) {
|
|
763
|
+
await assertYarnAvailable();
|
|
764
|
+
const { nodeDir } = resolveNpm();
|
|
765
|
+
const cacheDir = await mkdtemp(join(tmpdir(), "kici-yarn-cache-"));
|
|
766
|
+
const env = envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir);
|
|
767
|
+
const argv = buildYarnInstallArgs(cacheDir, args.hasPrivateRegistry);
|
|
768
|
+
try {
|
|
769
|
+
process.stderr.write(`[dep-installer:trace] running: yarn ${argv.join(" ")}\n`);
|
|
770
|
+
await execFileAsync("yarn", argv, {
|
|
771
|
+
cwd: args.kiciDir,
|
|
772
|
+
env,
|
|
773
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
774
|
+
maxBuffer: INSTALL_MAX_BUFFER
|
|
775
|
+
});
|
|
776
|
+
} finally {
|
|
777
|
+
await rm(cacheDir, {
|
|
778
|
+
recursive: true,
|
|
779
|
+
force: true
|
|
780
|
+
}).catch(() => {});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/** Throw an actionable error when the repo needs yarn but it is not installed. */
|
|
784
|
+
async function assertYarnAvailable() {
|
|
785
|
+
try {
|
|
786
|
+
await execFileAsync("yarn", ["--version"], {
|
|
787
|
+
timeout: 3e4,
|
|
788
|
+
cwd: tmpdir()
|
|
789
|
+
});
|
|
790
|
+
} catch (e) {
|
|
791
|
+
throw new Error(`This repository uses yarn, but yarn is not available on this agent. Install yarn (e.g. \`corepack enable\`) or run on a container/Firecracker agent that bundles it. (${toErrorMessage(e)})`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Build the in-repo workspace siblings `.kici` depends on (yarn links them on
|
|
796
|
+
* install but does not build them). Walks siblings from the resolved
|
|
797
|
+
* node_modules root and runs each sibling's `build` script in leaf-first
|
|
798
|
+
* (reverse-discovery) order with a clean env (no synthesized registry tokens).
|
|
799
|
+
* Deep cross-sibling build chains may build out of strict topological order —
|
|
800
|
+
* real `.kici` closures are shallow.
|
|
801
|
+
*/
|
|
802
|
+
async function buildYarnWorkspaceClosure(repoRoot, kiciDir) {
|
|
803
|
+
const siblings = await collectInRepoSiblings(repoRoot, kiciDir, resolveYarnNodeModulesRoot(repoRoot, kiciDir));
|
|
804
|
+
if (siblings.length === 0) return;
|
|
805
|
+
const { nodeDir } = resolveNpm();
|
|
806
|
+
const env = envWithNodeOnPath({}, nodeDir);
|
|
807
|
+
for (const rel of [...siblings].reverse()) {
|
|
808
|
+
const sibDir = join(repoRoot, rel);
|
|
809
|
+
if (!await siblingHasBuildScript(sibDir)) continue;
|
|
810
|
+
process.stderr.write(`[dep-installer:trace] building yarn sibling: yarn --cwd ${sibDir} run build\n`);
|
|
811
|
+
try {
|
|
812
|
+
await execFileAsync("yarn", [
|
|
813
|
+
"--cwd",
|
|
814
|
+
sibDir,
|
|
815
|
+
"run",
|
|
816
|
+
"build"
|
|
817
|
+
], {
|
|
818
|
+
cwd: repoRoot,
|
|
819
|
+
env,
|
|
820
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
821
|
+
maxBuffer: INSTALL_MAX_BUFFER
|
|
822
|
+
});
|
|
823
|
+
} catch (e) {
|
|
824
|
+
logSubprocessStreams(e, []);
|
|
825
|
+
throw new Error(`Failed to build .kici yarn workspace sibling ${rel}: ${describeExecError(e)}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
/** Whether a sibling package.json declares a `build` script. */
|
|
830
|
+
async function siblingHasBuildScript(sibDir) {
|
|
831
|
+
try {
|
|
832
|
+
return typeof JSON.parse(await readFile(join(sibDir, "package.json"), "utf-8")).scripts?.build === "string";
|
|
833
|
+
} catch {
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
623
837
|
/**
|
|
624
838
|
* Build the in-repo dependency closure of the `.kici/` package so a
|
|
625
839
|
* `workspace:` sibling's build output exists before the workflow that imports
|
|
@@ -663,7 +877,10 @@ function describeExecError(e) {
|
|
|
663
877
|
/** Throw an actionable error when the repo needs pnpm but it is not installed. */
|
|
664
878
|
async function assertPnpmAvailable() {
|
|
665
879
|
try {
|
|
666
|
-
await execFileAsync("pnpm", ["--version"], {
|
|
880
|
+
await execFileAsync("pnpm", ["--version"], {
|
|
881
|
+
timeout: 3e4,
|
|
882
|
+
cwd: tmpdir()
|
|
883
|
+
});
|
|
667
884
|
} catch (e) {
|
|
668
885
|
throw new Error(`This repository is a pnpm workspace, but pnpm is not available on this agent. Install pnpm (e.g. \`corepack enable\`) or run on a container/ Firecracker agent that bundles it. (${toErrorMessage(e)})`);
|
|
669
886
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type KiciBundle } from '@kici-dev/engine/provenance/bundle';
|
|
2
|
+
import type { OidcTokenResult } from '@kici-dev/engine/protocol/messages/oidc-token-relay';
|
|
3
|
+
import { type ProvenanceSubject } from './statement-builder.js';
|
|
4
|
+
export interface AttestDeps {
|
|
5
|
+
/** P1.4 relay: returns a KiCI ID token bound to the current job. */
|
|
6
|
+
getIdToken: (opts: {
|
|
7
|
+
audience: string;
|
|
8
|
+
}) => Promise<OidcTokenResult>;
|
|
9
|
+
/** Upload the serialized bundle; returns the storage key it was written to. */
|
|
10
|
+
persist: (bundle: KiciBundle, subjectDigest: string) => Promise<string>;
|
|
11
|
+
builderVersions: {
|
|
12
|
+
'kici-agent': string;
|
|
13
|
+
'kici-orchestrator': string;
|
|
14
|
+
};
|
|
15
|
+
/** Injectable clock for deterministic tests; defaults to wall time. */
|
|
16
|
+
now?: () => string;
|
|
17
|
+
}
|
|
18
|
+
export interface AttestInput {
|
|
19
|
+
subject: ProvenanceSubject;
|
|
20
|
+
audience?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface AttestResult {
|
|
23
|
+
storageKey: string;
|
|
24
|
+
bundle: KiciBundle;
|
|
25
|
+
subjectDigest: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function attestProvenance(deps: AttestDeps, input: AttestInput): Promise<AttestResult>;
|
|
28
|
+
/** Pick the primary digest (`sha256` preferred) as the storage-key discriminator. */
|
|
29
|
+
export declare function subjectDigestString(subject: ProvenanceSubject): string;
|
|
30
|
+
//# sourceMappingURL=attest.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ephemeral-key DSSE signer for KiCI provenance (Mode A).
|
|
3
|
+
*
|
|
4
|
+
* Generates a fresh in-process ES256 keypair (never persisted), DSSE-signs the
|
|
5
|
+
* PAE of the statement bytes with the private half, and returns the envelope
|
|
6
|
+
* plus the public JWK. The public key travels in the bundle so the verifier can
|
|
7
|
+
* check the signature; the key needs no separate trust root because the bundle's
|
|
8
|
+
* identity JWT (verified against the Platform JWKS) anchors the whole package.
|
|
9
|
+
*/
|
|
10
|
+
import { type JWK } from 'jose';
|
|
11
|
+
import { type DsseEnvelope } from '@kici-dev/engine/provenance/dsse';
|
|
12
|
+
export interface SignedStatement {
|
|
13
|
+
envelope: DsseEnvelope;
|
|
14
|
+
/** Ephemeral public key as a JWK, with `kid` = its RFC 7638 thumbprint. */
|
|
15
|
+
publicJwk: JWK & {
|
|
16
|
+
kid: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** DSSE-sign `statementBytes` with a fresh in-process ephemeral ES256 key. */
|
|
20
|
+
export declare function signStatementDsse(payloadType: string, statementBytes: Uint8Array): Promise<SignedStatement>;
|
|
21
|
+
//# sourceMappingURL=sign.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a SLSA v1.0 in-toto provenance statement from the server-truth identity
|
|
3
|
+
* token claims plus the caller-supplied subject. The build context comes
|
|
4
|
+
* entirely from the JWT claims (Platform-minted, unforgeable), so the
|
|
5
|
+
* statement's identity equals the token's identity by construction.
|
|
6
|
+
*/
|
|
7
|
+
import { type KiciProvenanceStatement } from '@kici-dev/engine/provenance/schema';
|
|
8
|
+
/** The KiCI identity-token claims the builder reads (Platform server-truth). */
|
|
9
|
+
export interface ProvenanceTokenClaims {
|
|
10
|
+
iss: string;
|
|
11
|
+
repository?: string | null;
|
|
12
|
+
ref?: string | null;
|
|
13
|
+
sha?: string | null;
|
|
14
|
+
workflow_ref?: string | null;
|
|
15
|
+
kici_run_id: string;
|
|
16
|
+
kici_job_id: string;
|
|
17
|
+
orchestrator_id?: string | null;
|
|
18
|
+
}
|
|
19
|
+
/** Caller-supplied artifact subject: a name plus a lowercase-hex digest map. */
|
|
20
|
+
export interface ProvenanceSubject {
|
|
21
|
+
name: string;
|
|
22
|
+
digest: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
export interface BuildStatementInput {
|
|
25
|
+
tokenClaims: ProvenanceTokenClaims;
|
|
26
|
+
subject: ProvenanceSubject;
|
|
27
|
+
builderVersions: {
|
|
28
|
+
'kici-agent': string;
|
|
29
|
+
'kici-orchestrator': string;
|
|
30
|
+
};
|
|
31
|
+
/** ISO-8601 timestamp with offset. */
|
|
32
|
+
startedOn: string;
|
|
33
|
+
/** ISO-8601 timestamp with offset. */
|
|
34
|
+
finishedOn: string;
|
|
35
|
+
}
|
|
36
|
+
/** Build a KiCI SLSA v1.0 provenance statement (validates against the P1.1 schema). */
|
|
37
|
+
export declare function buildProvenanceStatement(input: BuildStatementInput): KiciProvenanceStatement;
|
|
38
|
+
//# sourceMappingURL=statement-builder.d.ts.map
|