agentcert 0.2.5 → 0.2.6
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 +5 -0
- package/dist/artifact-manifest.js +37 -0
- package/dist/control-plane.js +31 -22
- package/dist/types.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,6 +49,11 @@ files, 10 MiB per file, and 50 MiB total. Skipped references are reported in
|
|
|
49
49
|
the CLI and hosted run timeline. Companion uploads are restricted to
|
|
50
50
|
PNG/JPEG/WebP, JSON/JSONL, HTML, PDF, and ZIP; other extensions are skipped
|
|
51
51
|
before they are read. Pass `--no-artifacts` to upload only the JSON bundle.
|
|
52
|
+
Hosted pushes embed an `agentcert.artifact_manifest.v0.1` declaration with the
|
|
53
|
+
normalized path, SHA-256 digest, byte size, and kind of every prepared
|
|
54
|
+
companion artifact. The control plane reports `complete` only after every
|
|
55
|
+
hosted object exactly matches that declaration; missing, skipped, undeclared,
|
|
56
|
+
or mismatched artifacts remain observable as `partial` or `rejected`.
|
|
52
57
|
Project API keys can create runs, record events, and upload evidence, but
|
|
53
58
|
cannot approve their own runtime actions.
|
|
54
59
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AGENTCERT_ARTIFACT_MANIFEST_VERSION, } from "./types.js";
|
|
3
|
+
export function buildArtifactManifest(artifacts) {
|
|
4
|
+
const entries = artifacts.map((artifact) => ({
|
|
5
|
+
path: normalizeManifestPath(artifact.sourcePath),
|
|
6
|
+
sha256: createHash("sha256").update(artifact.bytes).digest("hex"),
|
|
7
|
+
sizeBytes: artifact.bytes.byteLength,
|
|
8
|
+
kind: artifact.kind,
|
|
9
|
+
})).sort((left, right) => left.path.localeCompare(right.path));
|
|
10
|
+
const paths = new Set();
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
if (paths.has(entry.path))
|
|
13
|
+
throw new Error(`Artifact manifest contains duplicate path ${entry.path}.`);
|
|
14
|
+
paths.add(entry.path);
|
|
15
|
+
}
|
|
16
|
+
return { schemaVersion: AGENTCERT_ARTIFACT_MANIFEST_VERSION, entries };
|
|
17
|
+
}
|
|
18
|
+
export function withArtifactManifest(bundle, artifacts) {
|
|
19
|
+
return { ...bundle, artifactManifest: buildArtifactManifest(artifacts) };
|
|
20
|
+
}
|
|
21
|
+
export function serializeHostedEvidenceBundle(bundle, artifacts) {
|
|
22
|
+
const reconciledBundle = withArtifactManifest(bundle, artifacts);
|
|
23
|
+
return {
|
|
24
|
+
bundle: reconciledBundle,
|
|
25
|
+
bytes: new TextEncoder().encode(`${JSON.stringify(reconciledBundle, null, 2)}\n`),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function normalizeManifestPath(value) {
|
|
29
|
+
const path = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
30
|
+
if (!path || path.length > 1024 || path.startsWith("/") || /^[A-Za-z]:\//.test(path)) {
|
|
31
|
+
throw new Error(`Artifact manifest path must be a relative path: ${value}`);
|
|
32
|
+
}
|
|
33
|
+
if (path.split("/").some((segment) => segment === ".." || segment === "")) {
|
|
34
|
+
throw new Error(`Artifact manifest path cannot contain empty or parent segments: ${value}`);
|
|
35
|
+
}
|
|
36
|
+
return path;
|
|
37
|
+
}
|
package/dist/control-plane.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serializeHostedEvidenceBundle } from "./artifact-manifest.js";
|
|
1
2
|
import { MAX_REPORTED_COMPANION_ARTIFACT_SKIPS, } from "./companion-artifacts.js";
|
|
2
3
|
export class ControlPlaneRequestError extends Error {
|
|
3
4
|
status;
|
|
@@ -40,8 +41,16 @@ export async function pushEvidenceToControlPlane(options) {
|
|
|
40
41
|
}
|
|
41
42
|
const request = options.fetch ?? fetch;
|
|
42
43
|
const projectUrl = `${baseUrl}/v1/projects/${encodeURIComponent(options.projectId)}`;
|
|
43
|
-
const
|
|
44
|
-
const
|
|
44
|
+
const companionArtifacts = options.companionArtifacts ?? [];
|
|
45
|
+
const hostedEvidence = options.companionArtifacts === undefined && options.bundle.artifactManifest
|
|
46
|
+
? {
|
|
47
|
+
bundle: options.bundle,
|
|
48
|
+
bytes: new TextEncoder().encode(`${JSON.stringify(options.bundle, null, 2)}\n`),
|
|
49
|
+
}
|
|
50
|
+
: serializeHostedEvidenceBundle(options.bundle, companionArtifacts);
|
|
51
|
+
const bundle = hostedEvidence.bundle;
|
|
52
|
+
const externalId = options.externalId ?? bundle.runId;
|
|
53
|
+
const kind = hostedRunKind(bundle);
|
|
45
54
|
const headers = { authorization: `Bearer ${options.apiKey}` };
|
|
46
55
|
const run = await requestJson(request, `${projectUrl}/runs`, {
|
|
47
56
|
method: "POST",
|
|
@@ -49,11 +58,11 @@ export async function pushEvidenceToControlPlane(options) {
|
|
|
49
58
|
body: JSON.stringify({
|
|
50
59
|
externalId,
|
|
51
60
|
kind,
|
|
52
|
-
schemaVersion:
|
|
53
|
-
startedAt:
|
|
61
|
+
schemaVersion: bundle.schemaVersion,
|
|
62
|
+
startedAt: bundle.generatedAt,
|
|
54
63
|
metadata: {
|
|
55
|
-
subject:
|
|
56
|
-
products:
|
|
64
|
+
subject: bundle.subject,
|
|
65
|
+
products: bundle.summary.products,
|
|
57
66
|
},
|
|
58
67
|
}),
|
|
59
68
|
});
|
|
@@ -65,12 +74,12 @@ export async function pushEvidenceToControlPlane(options) {
|
|
|
65
74
|
sequence: 0,
|
|
66
75
|
type: "agentcert.evidence.created",
|
|
67
76
|
actor: "agentcert-cli",
|
|
68
|
-
occurredAt:
|
|
77
|
+
occurredAt: bundle.generatedAt,
|
|
69
78
|
payload: {
|
|
70
|
-
verdict:
|
|
71
|
-
totalEvidence:
|
|
72
|
-
criticalEvidence:
|
|
73
|
-
highEvidence:
|
|
79
|
+
verdict: bundle.verdict,
|
|
80
|
+
totalEvidence: bundle.summary.totalEvidence,
|
|
81
|
+
criticalEvidence: bundle.summary.criticalEvidence,
|
|
82
|
+
highEvidence: bundle.summary.highEvidence,
|
|
74
83
|
},
|
|
75
84
|
}],
|
|
76
85
|
}),
|
|
@@ -78,22 +87,21 @@ export async function pushEvidenceToControlPlane(options) {
|
|
|
78
87
|
const query = new URLSearchParams({
|
|
79
88
|
fileName: options.fileName ?? "agentcert-evidence.json",
|
|
80
89
|
kind: "evidence_bundle",
|
|
81
|
-
schemaVersion:
|
|
90
|
+
schemaVersion: bundle.schemaVersion,
|
|
82
91
|
runId: run.id,
|
|
83
92
|
});
|
|
84
93
|
const evidence = await requestJson(request, `${projectUrl}/evidence?${query}`, {
|
|
85
94
|
method: "POST",
|
|
86
95
|
headers: { ...headers, "content-type": "application/json" },
|
|
87
|
-
body: new Uint8Array(
|
|
96
|
+
body: new Uint8Array(hostedEvidence.bytes).buffer,
|
|
88
97
|
});
|
|
89
|
-
const companionArtifacts = options.companionArtifacts ?? [];
|
|
90
98
|
const skippedArtifacts = options.skippedCompanionArtifacts ?? [];
|
|
91
99
|
let artifactBytesUploaded = 0;
|
|
92
100
|
for (const artifact of companionArtifacts) {
|
|
93
101
|
const artifactQuery = new URLSearchParams({
|
|
94
102
|
fileName: artifact.fileName,
|
|
95
103
|
kind: artifact.kind,
|
|
96
|
-
schemaVersion:
|
|
104
|
+
schemaVersion: bundle.schemaVersion,
|
|
97
105
|
runId: run.id,
|
|
98
106
|
sourcePath: artifact.sourcePath,
|
|
99
107
|
});
|
|
@@ -113,7 +121,7 @@ export async function pushEvidenceToControlPlane(options) {
|
|
|
113
121
|
sequence: 1,
|
|
114
122
|
type: "agentcert.companion_artifacts.processed",
|
|
115
123
|
actor: "agentcert-cli",
|
|
116
|
-
occurredAt:
|
|
124
|
+
occurredAt: bundle.generatedAt,
|
|
117
125
|
payload: {
|
|
118
126
|
uploadedCount: companionArtifacts.length,
|
|
119
127
|
skippedCount: skippedArtifacts.length,
|
|
@@ -127,19 +135,20 @@ export async function pushEvidenceToControlPlane(options) {
|
|
|
127
135
|
}),
|
|
128
136
|
});
|
|
129
137
|
}
|
|
130
|
-
const firstDivergence =
|
|
138
|
+
const firstDivergence = bundle.evidence.find((item) => item.severity === "critical" || item.severity === "high")?.message;
|
|
131
139
|
await requestJson(request, `${projectUrl}/runs/${encodeURIComponent(run.id)}/complete`, {
|
|
132
140
|
method: "POST",
|
|
133
141
|
headers: { ...headers, "content-type": "application/json" },
|
|
134
142
|
body: JSON.stringify({
|
|
135
|
-
status:
|
|
136
|
-
score:
|
|
137
|
-
summary: `${
|
|
143
|
+
status: bundle.verdict.passed ? "passed" : "failed",
|
|
144
|
+
score: bundle.verdict.score,
|
|
145
|
+
summary: `${bundle.subject.name}: ${bundle.verdict.level}`,
|
|
138
146
|
firstDivergence,
|
|
139
|
-
completedAt:
|
|
147
|
+
completedAt: bundle.generatedAt,
|
|
140
148
|
metadata: {
|
|
141
149
|
evidenceId: evidence.id,
|
|
142
|
-
evidenceSchemaVersion:
|
|
150
|
+
evidenceSchemaVersion: bundle.schemaVersion,
|
|
151
|
+
artifactManifestVersion: bundle.artifactManifest?.schemaVersion,
|
|
143
152
|
companionArtifactsUploaded: companionArtifacts.length,
|
|
144
153
|
companionArtifactsSkipped: skippedArtifacts.length,
|
|
145
154
|
companionArtifactBytesUploaded: artifactBytesUploaded,
|
package/dist/types.js
CHANGED