agentcert 0.2.4 → 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 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/cli.js CHANGED
@@ -20,10 +20,17 @@ import { serveAgentCertMonitor } from "./local-server.js";
20
20
  import { parseSchemaId, validateAgentCertSchema } from "./schema-validator.js";
21
21
  import { applyRunOverrides, loadRunProfile, profileFromArtifactFlags, renderRunSummary, runAgentCertProfile, } from "./runner.js";
22
22
  import { buildRobustnessLabSnapshot, readRobustnessLabConfig, renderRobustnessLabSummary, writeRobustnessLabSnapshot } from "./lab.js";
23
+ import { renderCommandHelp } from "./command-help.js";
23
24
  process.on("uncaughtException", reportFatalError);
24
25
  process.on("unhandledRejection", reportFatalError);
25
26
  const command = process.argv[2] ?? "help";
26
- if (command === "init") {
27
+ const commandHelp = process.argv.some((argument) => argument === "--help" || argument === "-h")
28
+ ? renderCommandHelp(command)
29
+ : undefined;
30
+ if (commandHelp) {
31
+ process.stdout.write(commandHelp);
32
+ }
33
+ else if (command === "init") {
27
34
  const outPath = resolve(readFlag("--out") ?? "agentcert.config.json");
28
35
  const tripwireConfigPath = resolve(readFlag("--tripwire-config") ?? "tripwire.yml");
29
36
  const githubWorkflowPath = resolve(readFlag("--github-action-out") ?? ".github/workflows/agentcert-tripwire.yml");
@@ -0,0 +1,19 @@
1
+ export function renderCommandHelp(command) {
2
+ if (command !== "push")
3
+ return undefined;
4
+ return `Usage:
5
+ agentcert push --evidence .agentcert/latest/agentcert-evidence.json
6
+ agentcert push --evidence <path> --server <url> --project <project-id> [--api-key <key>]
7
+
8
+ Options:
9
+ --evidence <path> Evidence bundle (default: .agentcert/latest/agentcert-evidence.json)
10
+ --connection <name> Saved hosted connection
11
+ --server <url> Hosted AgentCert base URL
12
+ --project <id> Hosted project ID
13
+ --api-key <key> Project API key (prefer AGENTCERT_API_KEY in CI)
14
+ --external-id <id> Idempotent hosted run ID
15
+ --artifact-root <dir> Allowed root for companion artifacts (default: current directory)
16
+ --no-artifacts Upload only the evidence bundle
17
+ --help, -h Show this help
18
+ `;
19
+ }
@@ -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 externalId = options.externalId ?? options.bundle.runId;
44
- const kind = hostedRunKind(options.bundle);
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: options.bundle.schemaVersion,
53
- startedAt: options.bundle.generatedAt,
61
+ schemaVersion: bundle.schemaVersion,
62
+ startedAt: bundle.generatedAt,
54
63
  metadata: {
55
- subject: options.bundle.subject,
56
- products: options.bundle.summary.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: options.bundle.generatedAt,
77
+ occurredAt: bundle.generatedAt,
69
78
  payload: {
70
- verdict: options.bundle.verdict,
71
- totalEvidence: options.bundle.summary.totalEvidence,
72
- criticalEvidence: options.bundle.summary.criticalEvidence,
73
- highEvidence: options.bundle.summary.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: options.bundle.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(options.evidenceBytes).buffer,
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: options.bundle.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: options.bundle.generatedAt,
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 = options.bundle.evidence.find((item) => item.severity === "critical" || item.severity === "high")?.message;
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: options.bundle.verdict.passed ? "passed" : "failed",
136
- score: options.bundle.verdict.score,
137
- summary: `${options.bundle.subject.name}: ${options.bundle.verdict.level}`,
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: options.bundle.generatedAt,
147
+ completedAt: bundle.generatedAt,
140
148
  metadata: {
141
149
  evidenceId: evidence.id,
142
- evidenceSchemaVersion: options.bundle.schemaVersion,
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
@@ -1,2 +1,3 @@
1
1
  export const AGENTCERT_EVIDENCE_SCHEMA_VERSION = "agentcert.evidence.v0.1";
2
2
  export const AGENTCERT_EVIDENCE_SCHEMA_SEMVER = "0.1.0";
3
+ export const AGENTCERT_ARTIFACT_MANIFEST_VERSION = "agentcert.artifact_manifest.v0.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcert",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Assurance release gates, runtime evidence, and regression CI for AI agents.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",