agentcert 0.2.2 → 0.2.3
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/README.md +7 -2
- package/dist/artifact-validation.js +19 -11
- package/dist/cli.js +16 -1
- package/dist/companion-artifacts.js +180 -0
- package/dist/control-plane.js +53 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,8 +42,13 @@ npx agentcert push --evidence .agentcert/latest/agentcert-evidence.json
|
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Add `--push` to `agentcert run` to run locally and upload the resulting bundle
|
|
45
|
-
in one command.
|
|
46
|
-
|
|
45
|
+
in one command. By default, both commands also upload local files referenced by
|
|
46
|
+
the bundle. Reads are confined to `--artifact-root` (the current directory by
|
|
47
|
+
default), path and symlink escapes are rejected, and uploads are capped at 25
|
|
48
|
+
files, 10 MiB per file, and 50 MiB total. Skipped references are reported in
|
|
49
|
+
the CLI and hosted run timeline. Pass `--no-artifacts` to upload only the JSON
|
|
50
|
+
bundle. Project API keys can create runs, record events, and upload evidence,
|
|
51
|
+
but cannot approve their own runtime actions.
|
|
47
52
|
|
|
48
53
|
Review/export helpers:
|
|
49
54
|
|
|
@@ -4,21 +4,24 @@ export async function validateEvidenceArtifacts(input, artifactRoot) {
|
|
|
4
4
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
5
5
|
return { checked: 0, missing: [] };
|
|
6
6
|
}
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const uniquePaths = collectEvidenceArtifactPaths(input, artifactRoot)
|
|
8
|
+
.filter((entry) => !isRemoteArtifactPath(entry.sourcePath));
|
|
9
9
|
const missing = [];
|
|
10
10
|
for (const entry of uniquePaths) {
|
|
11
|
-
const fullPath = isAbsolute(entry.
|
|
11
|
+
const fullPath = isAbsolute(entry.sourcePath) ? entry.sourcePath : resolve(entry.root, entry.sourcePath);
|
|
12
12
|
try {
|
|
13
13
|
await access(fullPath);
|
|
14
14
|
}
|
|
15
15
|
catch {
|
|
16
|
-
missing.push(entry.
|
|
16
|
+
missing.push(entry.sourcePath);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
return { checked: uniquePaths.length, missing };
|
|
20
20
|
}
|
|
21
|
-
function
|
|
21
|
+
export function collectEvidenceArtifactPaths(input, artifactRoot) {
|
|
22
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
23
|
+
return [];
|
|
24
|
+
const bundle = input;
|
|
22
25
|
const paths = [];
|
|
23
26
|
const productRoots = productArtifactRoots(bundle, artifactRoot);
|
|
24
27
|
collectStringValues(bundle.artifacts, paths, artifactRoot);
|
|
@@ -27,20 +30,25 @@ function collectArtifactPaths(bundle, artifactRoot) {
|
|
|
27
30
|
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
28
31
|
const resultRecord = result;
|
|
29
32
|
collectStringValues(resultRecord.artifacts, paths, artifactRoot);
|
|
33
|
+
const product = typeof resultRecord.product === "string" ? resultRecord.product : undefined;
|
|
34
|
+
collectEvidencePaths(resultRecord.evidence, paths, product ? (productRoots.get(product) ?? artifactRoot) : artifactRoot);
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
|
-
|
|
37
|
+
collectEvidencePaths(bundle.evidence, paths, artifactRoot, productRoots);
|
|
38
|
+
return dedupeArtifactPathEntries(paths);
|
|
39
|
+
}
|
|
40
|
+
function collectEvidencePaths(input, paths, root, productRoots) {
|
|
41
|
+
const evidence = Array.isArray(input) ? input : [];
|
|
33
42
|
for (const item of evidence) {
|
|
34
43
|
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
35
44
|
const itemRecord = item;
|
|
36
45
|
const artifactPath = itemRecord.artifactPath;
|
|
37
46
|
if (typeof artifactPath === "string" && artifactPath.length > 0) {
|
|
38
47
|
const source = typeof itemRecord.source === "string" ? itemRecord.source : undefined;
|
|
39
|
-
paths.push({
|
|
48
|
+
paths.push({ sourcePath: artifactPath, root: source && productRoots ? (productRoots.get(source) ?? root) : root });
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
51
|
}
|
|
43
|
-
return paths;
|
|
44
52
|
}
|
|
45
53
|
function productArtifactRoots(bundle, artifactRoot) {
|
|
46
54
|
const roots = new Map();
|
|
@@ -71,7 +79,7 @@ function collectStringValues(input, paths, root) {
|
|
|
71
79
|
}
|
|
72
80
|
for (const value of Object.values(input)) {
|
|
73
81
|
if (typeof value === "string" && value.length > 0) {
|
|
74
|
-
paths.push({
|
|
82
|
+
paths.push({ sourcePath: value, root });
|
|
75
83
|
}
|
|
76
84
|
}
|
|
77
85
|
}
|
|
@@ -79,7 +87,7 @@ function dedupeArtifactPathEntries(entries) {
|
|
|
79
87
|
const seen = new Set();
|
|
80
88
|
const deduped = [];
|
|
81
89
|
for (const entry of entries) {
|
|
82
|
-
const key = `${entry.root}\0${entry.
|
|
90
|
+
const key = `${entry.root}\0${entry.sourcePath}`;
|
|
83
91
|
if (seen.has(key))
|
|
84
92
|
continue;
|
|
85
93
|
seen.add(key);
|
|
@@ -87,6 +95,6 @@ function dedupeArtifactPathEntries(entries) {
|
|
|
87
95
|
}
|
|
88
96
|
return deduped;
|
|
89
97
|
}
|
|
90
|
-
function
|
|
98
|
+
export function isRemoteArtifactPath(value) {
|
|
91
99
|
return /^[a-z][a-z0-9+.-]*:\/\//i.test(value);
|
|
92
100
|
}
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
5
5
|
import { validateEvidenceArtifacts } from "./artifact-validation.js";
|
|
6
6
|
import { renderAgentCertBadge } from "./badge.js";
|
|
7
7
|
import { buildEvidenceBundle } from "./bundle.js";
|
|
8
|
+
import { collectCompanionArtifacts, MAX_REPORTED_COMPANION_ARTIFACT_SKIPS } from "./companion-artifacts.js";
|
|
8
9
|
import { recordsFromAgentCertResult, evaluateFailureClassifier, renderCorpusSummary, summarizeCorpus, writeReviewedFailureDataset, } from "./corpus.js";
|
|
9
10
|
import { openCorpusStore, parseCorpusStoreKind } from "./corpus-store.js";
|
|
10
11
|
import { pushEvidenceToControlPlane, verifyControlPlaneConnection } from "./control-plane.js";
|
|
@@ -521,7 +522,7 @@ else {
|
|
|
521
522
|
agentcert monitor build --corpus .agentcert/corpus/corpus.jsonl --out .agentcert/latest/monitor.json --subject my-agent
|
|
522
523
|
agentcert monitor build --store sqlite --sqlite .agentcert/corpus/agentcert.sqlite --out .agentcert/latest/monitor.json --subject my-agent
|
|
523
524
|
agentcert run --profile public-demo
|
|
524
|
-
agentcert push --evidence .agentcert/latest/agentcert-evidence.json --server https://agentcert.example.com --project <project-id>
|
|
525
|
+
agentcert push --evidence .agentcert/latest/agentcert-evidence.json --server https://agentcert.example.com --project <project-id> [--artifact-root .] [--no-artifacts]
|
|
525
526
|
agentcert run --tripwire .tripwire/latest/tripwire-result.json --push
|
|
526
527
|
agentcert run --mcpbench .mcpbench/latest/results.json --tripwire .tripwire/latest/tripwire-result.json --onegent .onegent/procurement/audit-packet.json --out .agentcert/latest --corpus .agentcert/corpus/corpus.jsonl --monitor-out .agentcert/latest/monitor.json --reviewed-dataset-out .agentcert/latest/reviewed-failure-dataset.jsonl
|
|
527
528
|
agentcert release-gate --config agentcert.config.json --strict
|
|
@@ -551,6 +552,9 @@ async function pushHostedEvidence(bundle, bytes, fileName) {
|
|
|
551
552
|
projectId: readFlag("--project"),
|
|
552
553
|
apiKey: readFlag("--api-key"),
|
|
553
554
|
});
|
|
555
|
+
const companions = readBoolFlag("--no-artifacts")
|
|
556
|
+
? undefined
|
|
557
|
+
: await collectCompanionArtifacts(bundle, resolve(readFlag("--artifact-root") ?? process.cwd()));
|
|
554
558
|
const result = await pushEvidenceToControlPlane({
|
|
555
559
|
baseUrl: connection.server,
|
|
556
560
|
projectId: connection.projectId,
|
|
@@ -559,8 +563,19 @@ async function pushHostedEvidence(bundle, bytes, fileName) {
|
|
|
559
563
|
evidenceBytes: bytes,
|
|
560
564
|
fileName,
|
|
561
565
|
externalId: readFlag("--external-id"),
|
|
566
|
+
companionArtifacts: companions?.artifacts,
|
|
567
|
+
skippedCompanionArtifacts: companions?.skipped,
|
|
562
568
|
});
|
|
563
569
|
process.stdout.write(`Hosted run: ${result.runId}\nHosted evidence: ${result.evidenceId}\n`);
|
|
570
|
+
if (companions) {
|
|
571
|
+
process.stdout.write(`Hosted companion artifacts: ${result.artifactsUploaded} uploaded, ${result.artifactsSkipped} skipped, ${result.artifactBytesUploaded} bytes.\n`);
|
|
572
|
+
for (const skipped of companions.skipped.slice(0, MAX_REPORTED_COMPANION_ARTIFACT_SKIPS)) {
|
|
573
|
+
process.stderr.write(`Skipped companion artifact ${skipped.sourcePath}: ${skipped.reason}${skipped.detail ? ` (${skipped.detail})` : ""}.\n`);
|
|
574
|
+
}
|
|
575
|
+
if (companions.skipped.length > MAX_REPORTED_COMPANION_ARTIFACT_SKIPS) {
|
|
576
|
+
process.stderr.write(`${companions.skipped.length - MAX_REPORTED_COMPANION_ARTIFACT_SKIPS} additional skipped artifacts omitted.\n`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
564
579
|
}
|
|
565
580
|
async function promptValue(label) {
|
|
566
581
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { lstat, open, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { basename, extname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { collectEvidenceArtifactPaths, isRemoteArtifactPath, } from "./artifact-validation.js";
|
|
4
|
+
export const DEFAULT_COMPANION_ARTIFACT_LIMITS = {
|
|
5
|
+
maxFiles: 25,
|
|
6
|
+
maxFileBytes: 10 * 1024 * 1024,
|
|
7
|
+
maxTotalBytes: 50 * 1024 * 1024,
|
|
8
|
+
};
|
|
9
|
+
export const MAX_REPORTED_COMPANION_ARTIFACT_SKIPS = 50;
|
|
10
|
+
export async function collectCompanionArtifacts(bundle, artifactRoot, limits = DEFAULT_COMPANION_ARTIFACT_LIMITS) {
|
|
11
|
+
validateLimits(limits);
|
|
12
|
+
const root = await realpath(resolve(artifactRoot));
|
|
13
|
+
const artifacts = [];
|
|
14
|
+
const skipped = [];
|
|
15
|
+
const uploadedRealPaths = new Set();
|
|
16
|
+
let totalBytes = 0;
|
|
17
|
+
for (const entry of collectEvidenceArtifactPaths(bundle, root)) {
|
|
18
|
+
if (isRemoteArtifactPath(entry.sourcePath)) {
|
|
19
|
+
skipped.push(skip(entry, "remote_url"));
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (artifacts.length >= limits.maxFiles) {
|
|
23
|
+
skipped.push(skip(entry, "file_limit", `maximum ${limits.maxFiles} files`));
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const candidate = isAbsolute(entry.sourcePath)
|
|
27
|
+
? resolve(entry.sourcePath)
|
|
28
|
+
: resolve(entry.root, entry.sourcePath);
|
|
29
|
+
if (!isWithinRoot(root, candidate)) {
|
|
30
|
+
skipped.push(skip(entry, "outside_artifact_root"));
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
let candidateInfo;
|
|
34
|
+
try {
|
|
35
|
+
candidateInfo = await lstat(candidate);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
skipped.push(skip(entry, errorCode(error) === "ENOENT" ? "missing" : "unreadable", errorMessage(error)));
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (candidateInfo.isSymbolicLink()) {
|
|
42
|
+
skipped.push(skip(entry, "symlink"));
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
let resolvedPath;
|
|
46
|
+
try {
|
|
47
|
+
resolvedPath = await realpath(candidate);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
skipped.push(skip(entry, errorCode(error) === "ENOENT" ? "missing" : "unreadable", errorMessage(error)));
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (!isWithinRoot(root, resolvedPath)) {
|
|
54
|
+
skipped.push(skip(entry, "outside_artifact_root"));
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (uploadedRealPaths.has(resolvedPath))
|
|
58
|
+
continue;
|
|
59
|
+
let fileInfo;
|
|
60
|
+
try {
|
|
61
|
+
fileInfo = await stat(resolvedPath);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
skipped.push(skip(entry, errorCode(error) === "ENOENT" ? "missing" : "unreadable", errorMessage(error)));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!fileInfo.isFile()) {
|
|
68
|
+
skipped.push(skip(entry, "not_file"));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (fileInfo.size > limits.maxFileBytes) {
|
|
72
|
+
skipped.push(skip(entry, "file_too_large", `${fileInfo.size} bytes exceeds ${limits.maxFileBytes}`));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (totalBytes + fileInfo.size > limits.maxTotalBytes) {
|
|
76
|
+
skipped.push(skip(entry, "total_size_limit", `maximum ${limits.maxTotalBytes} bytes`));
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const remainingBytes = limits.maxTotalBytes - totalBytes;
|
|
80
|
+
let readResult;
|
|
81
|
+
try {
|
|
82
|
+
readResult = await readBounded(resolvedPath, Math.min(limits.maxFileBytes, remainingBytes));
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
skipped.push(skip(entry, "unreadable", errorMessage(error)));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (readResult.exceeded) {
|
|
89
|
+
const reason = remainingBytes < limits.maxFileBytes ? "total_size_limit" : "file_too_large";
|
|
90
|
+
skipped.push(skip(entry, reason));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const descriptor = describeArtifact(entry.sourcePath);
|
|
94
|
+
artifacts.push({
|
|
95
|
+
sourcePath: normalizeSourcePath(entry.sourcePath),
|
|
96
|
+
fileName: basename(resolvedPath),
|
|
97
|
+
kind: descriptor.kind,
|
|
98
|
+
contentType: descriptor.contentType,
|
|
99
|
+
bytes: readResult.bytes,
|
|
100
|
+
});
|
|
101
|
+
uploadedRealPaths.add(resolvedPath);
|
|
102
|
+
totalBytes += readResult.bytes.byteLength;
|
|
103
|
+
}
|
|
104
|
+
return { artifacts, skipped, totalBytes, limits: { ...limits } };
|
|
105
|
+
}
|
|
106
|
+
function validateLimits(limits) {
|
|
107
|
+
for (const [name, value] of Object.entries(limits)) {
|
|
108
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
109
|
+
throw new Error(`${name} must be a positive integer.`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function isWithinRoot(root, candidate) {
|
|
113
|
+
const pathFromRoot = relative(root, candidate);
|
|
114
|
+
return pathFromRoot === "" || (!pathFromRoot.startsWith(`..${sep}`) && pathFromRoot !== ".." && !isAbsolute(pathFromRoot));
|
|
115
|
+
}
|
|
116
|
+
async function readBounded(path, maxBytes) {
|
|
117
|
+
const handle = await open(path, "r");
|
|
118
|
+
const chunks = [];
|
|
119
|
+
let total = 0;
|
|
120
|
+
try {
|
|
121
|
+
while (total <= maxBytes) {
|
|
122
|
+
const capacity = Math.min(64 * 1024, maxBytes + 1 - total);
|
|
123
|
+
const chunk = Buffer.allocUnsafe(capacity);
|
|
124
|
+
const { bytesRead } = await handle.read(chunk, 0, capacity, null);
|
|
125
|
+
if (bytesRead === 0)
|
|
126
|
+
return { bytes: Buffer.concat(chunks, total), exceeded: false };
|
|
127
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
128
|
+
total += bytesRead;
|
|
129
|
+
}
|
|
130
|
+
return { bytes: new Uint8Array(), exceeded: true };
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
await handle.close();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function describeArtifact(path) {
|
|
137
|
+
const lower = path.toLowerCase();
|
|
138
|
+
const extension = extname(lower);
|
|
139
|
+
if ([".png", ".jpg", ".jpeg", ".webp"].includes(extension)) {
|
|
140
|
+
const imageType = extension === ".jpg" ? "jpeg" : extension.slice(1);
|
|
141
|
+
return { kind: "screenshot", contentType: `image/${imageType}` };
|
|
142
|
+
}
|
|
143
|
+
if (extension === ".svg")
|
|
144
|
+
return { kind: "report", contentType: "image/svg+xml" };
|
|
145
|
+
if (extension === ".zip" || lower.includes("trace")) {
|
|
146
|
+
const contentType = extension === ".zip" ? "application/zip" : extension === ".json" ? "application/json" : "application/octet-stream";
|
|
147
|
+
return { kind: "trace", contentType };
|
|
148
|
+
}
|
|
149
|
+
if ([".html", ".htm"].includes(extension) || lower.includes("dom")) {
|
|
150
|
+
const contentType = [".html", ".htm"].includes(extension)
|
|
151
|
+
? "text/html; charset=utf-8"
|
|
152
|
+
: extension === ".json"
|
|
153
|
+
? "application/json"
|
|
154
|
+
: "application/octet-stream";
|
|
155
|
+
return { kind: "dom", contentType };
|
|
156
|
+
}
|
|
157
|
+
if (extension === ".json")
|
|
158
|
+
return { kind: "json", contentType: "application/json" };
|
|
159
|
+
if (extension === ".jsonl")
|
|
160
|
+
return { kind: "json", contentType: "application/x-ndjson" };
|
|
161
|
+
if (extension === ".pdf")
|
|
162
|
+
return { kind: "report", contentType: "application/pdf" };
|
|
163
|
+
if (extension === ".md")
|
|
164
|
+
return { kind: "report", contentType: "text/markdown; charset=utf-8" };
|
|
165
|
+
if (extension === ".txt")
|
|
166
|
+
return { kind: "artifact", contentType: "text/plain; charset=utf-8" };
|
|
167
|
+
return { kind: "artifact", contentType: "application/octet-stream" };
|
|
168
|
+
}
|
|
169
|
+
function normalizeSourcePath(path) {
|
|
170
|
+
return path.replace(/\\/g, "/").slice(0, 1024);
|
|
171
|
+
}
|
|
172
|
+
function skip(entry, reason, detail) {
|
|
173
|
+
return { sourcePath: normalizeSourcePath(entry.sourcePath), reason, ...(detail ? { detail } : {}) };
|
|
174
|
+
}
|
|
175
|
+
function errorCode(error) {
|
|
176
|
+
return error && typeof error === "object" && "code" in error ? String(error.code) : undefined;
|
|
177
|
+
}
|
|
178
|
+
function errorMessage(error) {
|
|
179
|
+
return error instanceof Error ? error.message : String(error);
|
|
180
|
+
}
|
package/dist/control-plane.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MAX_REPORTED_COMPANION_ARTIFACT_SKIPS, } from "./companion-artifacts.js";
|
|
1
2
|
export class ControlPlaneRequestError extends Error {
|
|
2
3
|
status;
|
|
3
4
|
constructor(message, status) {
|
|
@@ -85,6 +86,47 @@ export async function pushEvidenceToControlPlane(options) {
|
|
|
85
86
|
headers: { ...headers, "content-type": "application/json" },
|
|
86
87
|
body: new Uint8Array(options.evidenceBytes).buffer,
|
|
87
88
|
});
|
|
89
|
+
const companionArtifacts = options.companionArtifacts ?? [];
|
|
90
|
+
const skippedArtifacts = options.skippedCompanionArtifacts ?? [];
|
|
91
|
+
let artifactBytesUploaded = 0;
|
|
92
|
+
for (const artifact of companionArtifacts) {
|
|
93
|
+
const artifactQuery = new URLSearchParams({
|
|
94
|
+
fileName: artifact.fileName,
|
|
95
|
+
kind: artifact.kind,
|
|
96
|
+
schemaVersion: options.bundle.schemaVersion,
|
|
97
|
+
runId: run.id,
|
|
98
|
+
sourcePath: artifact.sourcePath,
|
|
99
|
+
});
|
|
100
|
+
await requestJson(request, `${projectUrl}/evidence?${artifactQuery}`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: { ...headers, "content-type": artifact.contentType },
|
|
103
|
+
body: new Uint8Array(artifact.bytes).buffer,
|
|
104
|
+
});
|
|
105
|
+
artifactBytesUploaded += artifact.bytes.byteLength;
|
|
106
|
+
}
|
|
107
|
+
if (companionArtifacts.length > 0 || skippedArtifacts.length > 0) {
|
|
108
|
+
await requestJson(request, `${projectUrl}/runs/${encodeURIComponent(run.id)}/events`, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: { ...headers, "content-type": "application/json" },
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
events: [{
|
|
113
|
+
sequence: 1,
|
|
114
|
+
type: "agentcert.companion_artifacts.processed",
|
|
115
|
+
actor: "agentcert-cli",
|
|
116
|
+
occurredAt: options.bundle.generatedAt,
|
|
117
|
+
payload: {
|
|
118
|
+
uploadedCount: companionArtifacts.length,
|
|
119
|
+
skippedCount: skippedArtifacts.length,
|
|
120
|
+
uploadedBytes: artifactBytesUploaded,
|
|
121
|
+
skipped: skippedArtifacts
|
|
122
|
+
.slice(0, MAX_REPORTED_COMPANION_ARTIFACT_SKIPS)
|
|
123
|
+
.map(({ sourcePath, reason }) => ({ sourcePath, reason })),
|
|
124
|
+
skippedDetailsTruncated: skippedArtifacts.length > MAX_REPORTED_COMPANION_ARTIFACT_SKIPS,
|
|
125
|
+
},
|
|
126
|
+
}],
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
88
130
|
const firstDivergence = options.bundle.evidence.find((item) => item.severity === "critical" || item.severity === "high")?.message;
|
|
89
131
|
await requestJson(request, `${projectUrl}/runs/${encodeURIComponent(run.id)}/complete`, {
|
|
90
132
|
method: "POST",
|
|
@@ -98,10 +140,20 @@ export async function pushEvidenceToControlPlane(options) {
|
|
|
98
140
|
metadata: {
|
|
99
141
|
evidenceId: evidence.id,
|
|
100
142
|
evidenceSchemaVersion: options.bundle.schemaVersion,
|
|
143
|
+
companionArtifactsUploaded: companionArtifacts.length,
|
|
144
|
+
companionArtifactsSkipped: skippedArtifacts.length,
|
|
145
|
+
companionArtifactBytesUploaded: artifactBytesUploaded,
|
|
101
146
|
},
|
|
102
147
|
}),
|
|
103
148
|
});
|
|
104
|
-
return {
|
|
149
|
+
return {
|
|
150
|
+
runId: run.id,
|
|
151
|
+
evidenceId: evidence.id,
|
|
152
|
+
externalId,
|
|
153
|
+
artifactsUploaded: companionArtifacts.length,
|
|
154
|
+
artifactsSkipped: skippedArtifacts.length,
|
|
155
|
+
artifactBytesUploaded,
|
|
156
|
+
};
|
|
105
157
|
}
|
|
106
158
|
function hostedRunKind(bundle) {
|
|
107
159
|
const products = new Set(bundle.summary.products);
|