agentcert 0.1.1 → 0.1.2

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
@@ -12,6 +12,9 @@ badges, and accumulates a local failure corpus.
12
12
  npx agentcert init --subject my-browser-agent
13
13
  ```
14
14
 
15
+ This writes `agentcert.config.json` and `tripwire.yml`. Add `--github-action`
16
+ when you also want `.github/workflows/agentcert-tripwire.yml`.
17
+
15
18
  Edit `tripwire.yml` so `startUrl` and `agent.command` point at your app and
16
19
  browser agent. After Tripwire has produced `.tripwire/latest/tripwire-result.json`,
17
20
  build the AgentCert outputs:
@@ -37,10 +40,18 @@ Review/export helpers:
37
40
  npx agentcert corpus metrics --corpus .agentcert/corpus/corpus.jsonl
38
41
  npx agentcert corpus export-reviewed --corpus .agentcert/corpus/corpus.jsonl --out .agentcert/latest/reviewed-failure-dataset.jsonl
39
42
  npx agentcert corpus classifier-eval --corpus .agentcert/corpus/corpus.jsonl --out .agentcert/latest/failure-classifier-evaluation.json
43
+ npx agentcert validate .agentcert/latest/agentcert-evidence.json
44
+ npx agentcert validate .agentcert/latest/agentcert-evidence.json --check-artifacts
40
45
  npx agentcert schema validate --schema evidence-bundle --file .agentcert/latest/agentcert-evidence.json
41
46
  npx agentcert schema validate --schema classifier-eval --file examples/agentcert/classifier-eval.example.json
42
47
  ```
43
48
 
49
+ Release gate checklist:
50
+
51
+ ```text
52
+ docs/release-gate-checklist.md
53
+ ```
54
+
44
55
  CI users can run Tripwire and AgentCert together with
45
56
  `Kakarottoooo/agentcert/actions/tripwire@v0`.
46
57
 
@@ -49,6 +60,18 @@ Playwright-based agents over the same fault suite:
49
60
 
50
61
  https://kakarottoooo.github.io/agentcert/public-demo/real-agent-robustness/
51
62
 
63
+ Minimal no-key browser-agent example in the GitHub repo:
64
+
65
+ ```text
66
+ examples/minimal-browser-agent/
67
+ ```
68
+
69
+ External integration smoke matrix in the GitHub repo:
70
+
71
+ ```text
72
+ examples/real-agents/external-integration-smokes.md
73
+ ```
74
+
52
75
  Corpus storage:
53
76
 
54
77
  ```bash
@@ -0,0 +1,92 @@
1
+ import { access } from "node:fs/promises";
2
+ import { dirname, isAbsolute, resolve } from "node:path";
3
+ export async function validateEvidenceArtifacts(input, artifactRoot) {
4
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
5
+ return { checked: 0, missing: [] };
6
+ }
7
+ const paths = collectArtifactPaths(input, artifactRoot);
8
+ const uniquePaths = dedupeArtifactPathEntries(paths).filter((entry) => !looksLikeUrl(entry.path));
9
+ const missing = [];
10
+ for (const entry of uniquePaths) {
11
+ const fullPath = isAbsolute(entry.path) ? entry.path : resolve(entry.root, entry.path);
12
+ try {
13
+ await access(fullPath);
14
+ }
15
+ catch {
16
+ missing.push(entry.path);
17
+ }
18
+ }
19
+ return { checked: uniquePaths.length, missing };
20
+ }
21
+ function collectArtifactPaths(bundle, artifactRoot) {
22
+ const paths = [];
23
+ const productRoots = productArtifactRoots(bundle, artifactRoot);
24
+ collectStringValues(bundle.artifacts, paths, artifactRoot);
25
+ const results = Array.isArray(bundle.results) ? bundle.results : [];
26
+ for (const result of results) {
27
+ if (result && typeof result === "object" && !Array.isArray(result)) {
28
+ const resultRecord = result;
29
+ collectStringValues(resultRecord.artifacts, paths, artifactRoot);
30
+ }
31
+ }
32
+ const evidence = Array.isArray(bundle.evidence) ? bundle.evidence : [];
33
+ for (const item of evidence) {
34
+ if (item && typeof item === "object" && !Array.isArray(item)) {
35
+ const itemRecord = item;
36
+ const artifactPath = itemRecord.artifactPath;
37
+ if (typeof artifactPath === "string" && artifactPath.length > 0) {
38
+ const source = typeof itemRecord.source === "string" ? itemRecord.source : undefined;
39
+ paths.push({ path: artifactPath, root: source ? (productRoots.get(source) ?? artifactRoot) : artifactRoot });
40
+ }
41
+ }
42
+ }
43
+ return paths;
44
+ }
45
+ function productArtifactRoots(bundle, artifactRoot) {
46
+ const roots = new Map();
47
+ const results = Array.isArray(bundle.results) ? bundle.results : [];
48
+ for (const result of results) {
49
+ if (!result || typeof result !== "object" || Array.isArray(result))
50
+ continue;
51
+ const resultRecord = result;
52
+ const product = resultRecord.product;
53
+ const artifacts = resultRecord.artifacts;
54
+ if (typeof product !== "string" || !artifacts || typeof artifacts !== "object" || Array.isArray(artifacts))
55
+ continue;
56
+ const artifactRecord = artifacts;
57
+ const outDir = artifactRecord.outDir;
58
+ const resultPath = artifactRecord.result ?? artifactRecord.results;
59
+ if (typeof outDir === "string" && outDir.length > 0) {
60
+ roots.set(product, isAbsolute(outDir) ? outDir : resolve(artifactRoot, outDir));
61
+ }
62
+ else if (typeof resultPath === "string" && resultPath.length > 0) {
63
+ roots.set(product, dirname(isAbsolute(resultPath) ? resultPath : resolve(artifactRoot, resultPath)));
64
+ }
65
+ }
66
+ return roots;
67
+ }
68
+ function collectStringValues(input, paths, root) {
69
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
70
+ return;
71
+ }
72
+ for (const value of Object.values(input)) {
73
+ if (typeof value === "string" && value.length > 0) {
74
+ paths.push({ path: value, root });
75
+ }
76
+ }
77
+ }
78
+ function dedupeArtifactPathEntries(entries) {
79
+ const seen = new Set();
80
+ const deduped = [];
81
+ for (const entry of entries) {
82
+ const key = `${entry.root}\0${entry.path}`;
83
+ if (seen.has(key))
84
+ continue;
85
+ seen.add(key);
86
+ deduped.push(entry);
87
+ }
88
+ return deduped;
89
+ }
90
+ function looksLikeUrl(value) {
91
+ return /^[a-z][a-z0-9+.-]*:\/\//i.test(value);
92
+ }
package/dist/bundle.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { AGENTCERT_EVIDENCE_SCHEMA_SEMVER, AGENTCERT_EVIDENCE_SCHEMA_VERSION } from "./types.js";
1
2
  export function buildEvidenceBundle(results, subjectName, subjectType = "agent") {
2
3
  const evidence = results.flatMap((result) => result.evidence);
3
4
  const score = results.length === 0 ? 0 : Math.round(results.reduce((sum, result) => sum + result.score, 0) / results.length);
@@ -12,8 +13,8 @@ export function buildEvidenceBundle(results, subjectName, subjectType = "agent")
12
13
  }
13
14
  return {
14
15
  schemaName: "agentcert.evidence_bundle",
15
- schemaVersion: "1",
16
- schemaSemver: "1.0.0",
16
+ schemaVersion: AGENTCERT_EVIDENCE_SCHEMA_VERSION,
17
+ schemaSemver: AGENTCERT_EVIDENCE_SCHEMA_SEMVER,
17
18
  kind: "agentcert.evidence_bundle",
18
19
  runId: `agentcert_${Date.now()}`,
19
20
  generatedAt: new Date().toISOString(),
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { dirname, resolve } from "node:path";
4
+ import { validateEvidenceArtifacts } from "./artifact-validation.js";
4
5
  import { renderAgentCertBadge } from "./badge.js";
5
6
  import { buildEvidenceBundle } from "./bundle.js";
6
7
  import { recordsFromAgentCertResult, evaluateFailureClassifier, renderCorpusSummary, summarizeCorpus, writeReviewedFailureDataset, } from "./corpus.js";
@@ -13,6 +14,8 @@ import { serveAgentCertMonitor } from "./local-server.js";
13
14
  import { parseSchemaId, validateAgentCertSchema } from "./schema-validator.js";
14
15
  import { loadRunProfile, profileFromArtifactFlags, renderRunSummary, runAgentCertProfile, } from "./runner.js";
15
16
  import { buildRobustnessLabSnapshot, readRobustnessLabConfig, renderRobustnessLabSummary, writeRobustnessLabSnapshot } from "./lab.js";
17
+ process.on("uncaughtException", reportFatalError);
18
+ process.on("unhandledRejection", reportFatalError);
16
19
  const command = process.argv[2] ?? "help";
17
20
  if (command === "init") {
18
21
  const outPath = resolve(readFlag("--out") ?? "agentcert.config.json");
@@ -20,6 +23,7 @@ if (command === "init") {
20
23
  const githubWorkflowPath = resolve(readFlag("--github-action-out") ?? ".github/workflows/agentcert-tripwire.yml");
21
24
  const subject = readFlag("--subject") ?? "my-browser-agent";
22
25
  const force = readBoolFlag("--force");
26
+ const writeGitHubAction = (readBoolFlag("--github-action") || process.argv.includes("--github-action-out")) && !readBoolFlag("--skip-github-action");
23
27
  const config = {
24
28
  schemaVersion: "1",
25
29
  subject: {
@@ -60,15 +64,15 @@ if (command === "init") {
60
64
  await writeStarterFile(tripwireConfigPath, starterTripwireConfig(subject), force);
61
65
  process.stdout.write(`Wrote ${tripwireConfigPath}\n`);
62
66
  }
63
- if (!readBoolFlag("--skip-github-action")) {
67
+ if (writeGitHubAction) {
64
68
  await writeStarterFile(githubWorkflowPath, starterGitHubActionWorkflow(subject), force);
65
69
  process.stdout.write(`Wrote ${githubWorkflowPath}\n`);
66
70
  }
67
71
  process.stdout.write(`
68
72
  Next:
69
73
  1. Edit tripwire.yml so startUrl and agent.command/agent.args match your app and browser agent.
70
- 2. Commit .github/workflows/agentcert-tripwire.yml to run AgentCert in CI.
71
- 3. Or run locally after Tripwire writes .tripwire/latest/tripwire-result.json:
74
+ 2. Run in CI with Kakarottoooo/agentcert/actions/tripwire@v0, or re-run init with --github-action to write a workflow template.
75
+ 3. Run locally after Tripwire writes .tripwire/latest/tripwire-result.json:
72
76
  npx agentcert run --tripwire .tripwire/latest/tripwire-result.json --subject ${JSON.stringify(subject)} --fail-on-verdict
73
77
  `);
74
78
  }
@@ -317,10 +321,41 @@ else if (command === "schema") {
317
321
  `);
318
322
  }
319
323
  }
324
+ else if (command === "validate") {
325
+ const schema = parseSchemaId(readFlag("--schema") ?? "evidence-bundle");
326
+ const file = readFlag("--file") ?? readFirstPositionalAfterCommand();
327
+ if (!file) {
328
+ throw new Error("Missing evidence file. Usage: agentcert validate <file> [--schema evidence-bundle].");
329
+ }
330
+ const input = await readJson(file);
331
+ const result = validateAgentCertSchema(schema, input);
332
+ if (result.valid) {
333
+ process.stdout.write(`Valid ${schema}: ${resolve(file)}\n`);
334
+ }
335
+ else {
336
+ process.stdout.write(`Invalid ${schema}: ${resolve(file)}\n`);
337
+ for (const error of result.errors) {
338
+ process.stdout.write(`- ${error}\n`);
339
+ }
340
+ process.exitCode = 1;
341
+ }
342
+ if (result.valid && schema === "evidence-bundle" && readBoolFlag("--check-artifacts")) {
343
+ const artifactResult = await validateEvidenceArtifacts(input, resolve(readFlag("--artifact-root") ?? process.cwd()));
344
+ process.stdout.write(`Artifact paths checked: ${artifactResult.checked}\n`);
345
+ if (artifactResult.missing.length > 0) {
346
+ process.stdout.write("Missing artifact paths:\n");
347
+ for (const missing of artifactResult.missing) {
348
+ process.stdout.write(`- ${missing}\n`);
349
+ }
350
+ process.exitCode = 1;
351
+ }
352
+ }
353
+ }
320
354
  else {
321
355
  process.stdout.write(`Usage:
322
356
  agentcert init --subject my-browser-agent
323
357
  agentcert init --out agentcert.config.json --tripwire-config tripwire.yml --force
358
+ agentcert init --subject my-browser-agent --github-action
324
359
  agentcert report --mcpbench .mcpbench/latest/results.json --tripwire .tripwire/latest/tripwire-result.json --onegent .onegent/procurement/audit-packet.json --out .agentcert/latest --subject my-agent
325
360
  agentcert corpus ingest --tripwire .tripwire/latest/tripwire-result.json --out .agentcert/corpus/corpus.jsonl --subject my-agent
326
361
  agentcert corpus review --corpus .agentcert/corpus/corpus.jsonl --reviews .agentcert/corpus/failure-reviews.jsonl --pattern-key <failure-key> --type wrong_click --status corrected
@@ -332,6 +367,8 @@ else {
332
367
  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
333
368
  agentcert lab build --config examples/real-agents/robustness-lab/lab.config.json --out public-demo/real-agent-robustness/evidence/lab-snapshot.json
334
369
  agentcert serve --corpus .agentcert/corpus/corpus.jsonl --static public-demo/agentcert-monitor --artifact-root public-demo/browser-agent-robustness/evidence/tripwire-public-demo
370
+ agentcert validate .agentcert/latest/agentcert-evidence.json
371
+ agentcert validate .agentcert/latest/agentcert-evidence.json --check-artifacts
335
372
  agentcert schema validate --schema evidence-bundle --file .agentcert/latest/agentcert-evidence.json
336
373
  `);
337
374
  }
@@ -368,6 +405,19 @@ function readFlag(name) {
368
405
  const index = process.argv.indexOf(name);
369
406
  return index >= 0 ? process.argv[index + 1] : undefined;
370
407
  }
408
+ function readFirstPositionalAfterCommand() {
409
+ for (let index = 3; index < process.argv.length; index += 1) {
410
+ const value = process.argv[index];
411
+ if (!value)
412
+ continue;
413
+ if (value.startsWith("--")) {
414
+ index += 1;
415
+ continue;
416
+ }
417
+ return value;
418
+ }
419
+ return undefined;
420
+ }
371
421
  function readBoolFlag(name) {
372
422
  return process.argv.includes(name);
373
423
  }
@@ -467,6 +517,38 @@ async function writeStarterFile(path, content, force) {
467
517
  throw error;
468
518
  }
469
519
  }
520
+ function reportFatalError(error) {
521
+ process.stderr.write(formatCliError(error));
522
+ process.exit(1);
523
+ }
524
+ function formatCliError(error) {
525
+ const message = error instanceof Error ? error.message : String(error);
526
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : undefined;
527
+ const detail = [`AgentCert error: ${message}`];
528
+ const hint = errorHint(message, code);
529
+ if (hint) {
530
+ detail.push("", hint);
531
+ }
532
+ return `${detail.join("\n")}\n`;
533
+ }
534
+ function errorHint(message, code) {
535
+ if (code === "ENOENT") {
536
+ return "Hint: check the artifact path. Run `npx agentcert init` for starter config, then run Tripwire before `npx agentcert run`.";
537
+ }
538
+ if (message.includes("agentcert run requires")) {
539
+ return "Hint: run `npx agentcert init`, pass `--tripwire .tripwire/latest/tripwire-result.json`, or use `--config agentcert.config.json`.";
540
+ }
541
+ if (message.includes("No AgentCert artifacts were loaded") || message.includes("No input artifacts were provided")) {
542
+ return "Hint: provide at least one artifact with `--tripwire`, `--mcpbench`, `--onegent`, or configure `agentcert.config.json`.";
543
+ }
544
+ if (message.includes("Unexpected token") || message.includes("JSON")) {
545
+ return "Hint: AgentCert expected JSON. Check that the artifact file exists and was not replaced by logs or HTML.";
546
+ }
547
+ if (message.includes("already exists")) {
548
+ return "Hint: re-run with `--force` to overwrite starter files, or choose a different `--out` path.";
549
+ }
550
+ return undefined;
551
+ }
470
552
  function starterTripwireConfig(subject) {
471
553
  return `version: "0.1"
472
554
  project: ${JSON.stringify(subject)}
@@ -1,3 +1,4 @@
1
+ import { AGENTCERT_EVIDENCE_SCHEMA_SEMVER, AGENTCERT_EVIDENCE_SCHEMA_VERSION } from "./types.js";
1
2
  export function parseSchemaId(input) {
2
3
  const value = input ?? "evidence-bundle";
3
4
  if (value === "evidence-bundle" ||
@@ -34,8 +35,8 @@ export function validateAgentCertSchema(schema, input) {
34
35
  }
35
36
  function validateEvidenceBundle(value, errors) {
36
37
  requiredConst(value, "schemaName", "agentcert.evidence_bundle", errors);
37
- requiredConst(value, "schemaVersion", "1", errors);
38
- requiredConst(value, "schemaSemver", "1.0.0", errors);
38
+ requiredConst(value, "schemaVersion", AGENTCERT_EVIDENCE_SCHEMA_VERSION, errors);
39
+ requiredConst(value, "schemaSemver", AGENTCERT_EVIDENCE_SCHEMA_SEMVER, errors);
39
40
  requiredConst(value, "kind", "agentcert.evidence_bundle", errors);
40
41
  requiredString(value, "runId", errors);
41
42
  requiredString(value, "generatedAt", errors);
package/dist/types.js CHANGED
@@ -1 +1,2 @@
1
- export {};
1
+ export const AGENTCERT_EVIDENCE_SCHEMA_VERSION = "agentcert.evidence.v0.1";
2
+ export const AGENTCERT_EVIDENCE_SCHEMA_SEMVER = "0.1.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcert",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Regression CI, evidence reports, and failure corpus tooling for browser agents.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",