← Files ECZ-ID API TrustARCHIVED FILE
skills/api-trust-review/scripts/review.mjs
19.2 KB · Oct 3, 2026 · 06:34 UTC
#!/usr/bin/env node
// ECZ-ID API Trust: portable evidence review (generated by the ECZ-ID Plugin Foundry; do not edit).
// Reads file NAMES and PATHS under the given root. Opens no file. No network. Writes nothing.
// Same detectors, Review Priority rules and next actions as the ECZ-ID VS Code extension.
import { readdirSync } from "node:fs";
import { join, relative } from "node:path";
export const DEFAULT_IGNORES = new Set(["node_modules", ".git", ".pnpm-store", "dist", "out", "build", ".next", ".turbo", ".venv", "venv", "__pycache__", "target", "coverage"]);
export const DOT_ALLOWLIST = new Set([".github", ".gitlab", ".well-known"]);
/** Workspace-relative file paths, filename and path only. Dot-entries are skipped except the allowlist. */
export function listFiles(root, { maxDepth = 8, maxFiles = 20000, extraDotEntries = [] } = {}) {
const results = [];
const dots = new Set([...DOT_ALLOWLIST, ...extraDotEntries]);
const walk = (dir, depth) => {
if (depth > maxDepth || results.length >= maxFiles) return;
let entries;
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
for (const e of entries) {
if (results.length >= maxFiles) return;
if (e.name.startsWith(".") && !dots.has(e.name)) continue;
const full = join(dir, e.name);
if (e.isDirectory()) { if (DEFAULT_IGNORES.has(e.name)) continue; walk(full, depth + 1); }
else if (e.isFile()) results.push(relative(root, full).split("\\").join("/"));
}
};
walk(root, 0);
return results;
}
const RESOLVER_REF = [/(^|\/)\.well-known\/ecz-[a-z0-9-]*\.json$/i, /(^|\/)ecz-(agent|mcp|id)[a-z0-9-]*\.json$/i, /(^|\/)ecz-[a-z0-9-]+\.json$/i];
const REASON = {
EVIDENCE_OBSERVED: "Evidence observed locally for the items listed.",
EVIDENCE_NOT_OBSERVED: "Some expected evidence was not observed locally. This is neutral. It does not mean a problem exists.",
REVIEW_RECOMMENDED: "Observed evidence may still need human review before reliance.",
NO_PUBLIC_PROOF_REFERENCE: "No public resolver proof reference was found yet. This does not mean unsafe. Local policy decides.",
PARTIAL_PUBLIC_PROOF: "Partial public proof reference detected. Resolver-verifiable proof may make this easier to review.",
RECHECK_BEFORE_RELIANCE: "Re-check before reliance. Results reflect the workspace at scan time.",
LOCAL_POLICY_DECIDES: "Your local policy decides whether the observed evidence is sufficient."
};
function matchAny(patterns, files) {
for (const f of files) for (const re of patterns) if (re.test(f)) return f;
return undefined;
}
/** Same semantics as family/detect.ts detectEvidence. */
export function detectEvidence(spec, files, workspaceName) {
const observed = [], notObserved = [], reviewRequired = [];
for (const d of spec.detectors) {
const hit = matchAny(d.patterns.map((p) => new RegExp(p, "i")), files);
if (hit) {
const item = { id: d.id, label: d.label, status: "observed", detail: d.observedDetail, path: hit };
observed.push(item);
if (d.reviewWhenObserved) reviewRequired.push({ ...item, status: "review-required" });
} else notObserved.push({ id: d.id, label: d.label, status: "not-observed", detail: d.notObservedDetail });
}
const codes = [];
if (observed.length) codes.push("EVIDENCE_OBSERVED");
if (notObserved.length) codes.push("EVIDENCE_NOT_OBSERVED");
if (reviewRequired.length) codes.push("REVIEW_RECOMMENDED");
codes.push(matchAny(RESOLVER_REF, files) ? "PARTIAL_PUBLIC_PROOF" : "NO_PUBLIC_PROOF_REFERENCE");
codes.push("LOCAL_POLICY_DECIDES", "RECHECK_BEFORE_RELIANCE");
return { specialistId: spec.extensionId ?? spec.name, scannedAt: new Date().toISOString(), workspaceName, observed, notObserved, reviewRequired, reasonCodes: codes.map((id) => ({ id, message: REASON[id] })) };
}
export const PRIORITY_DISCLAIMER = "Review Priority is not a safety, approval or compliance determination. It indicates how much attention this evidence review deserves, based only on what was observed locally by filename and path.";
export const PRIORITY_MEANING = {
LOW: "Every evidence class this review looks for was observed. Review the documents themselves before reliance.",
NORMAL: "Observed evidence still needs human review, or supporting evidence was not observed. Worth completing before the next review.",
ELEVATED: "A primary evidence class was not observed. Review before you rely on this workspace as an evidence source.",
HIGH: "Evidence that a regulator, auditor or customer is likely to ask for first was not observed, or several primary classes are missing together."
};
export const ELEVATED_GAP_AGGREGATION_THRESHOLD = 2;
const RANK = { LOW: 0, NORMAL: 1, ELEVATED: 2, HIGH: 3 };
const FROM_WEIGHT = { high: "HIGH", elevated: "ELEVATED", normal: "NORMAL" };
/** Same rules as family/valueLayer.ts computeEvidenceReviewPriority. */
export function computeReviewPriority(spec, result, profile) {
const observed = new Map(result.observed.map((i) => [i.id, i]));
const review = new Set(result.reviewRequired.map((i) => i.id));
const lines = [];
for (const d of spec.detectors) {
const g = profile.guidance.find((x) => x.detectorId === d.id);
if (observed.has(d.id)) {
const needs = review.has(d.id);
lines.push({ detectorId: d.id, label: d.label, status: needs ? "review-required" : "observed", weight: "none", contributes: needs ? "NORMAL" : "LOW", detail: needs ? "Observed by filename and path; the document itself still needs human review." : "Observed by filename and path." });
} else {
const w = g?.weightWhenNotObserved ?? "normal";
lines.push({ detectorId: d.id, label: d.label, status: "not-observed", weight: w, contributes: FROM_WEIGHT[w], detail: `Not observed by filename and path (${w === "normal" ? "supporting" : "primary"} evidence class).` });
}
}
const counts = { LOW: 0, NORMAL: 0, ELEVATED: 0, HIGH: 0 };
for (const l of lines) counts[l.contributes]++;
let priority, rationale;
if (counts.HIGH > 0) { priority = "HIGH"; rationale = `HIGH because ${counts.HIGH} evidence class${counts.HIGH === 1 ? "" : "es"} that ${counts.HIGH === 1 ? "is" : "are"} usually requested first ${counts.HIGH === 1 ? "was" : "were"} not observed.`; }
else if (counts.ELEVATED >= ELEVATED_GAP_AGGREGATION_THRESHOLD) { priority = "HIGH"; rationale = `HIGH because ${counts.ELEVATED} primary evidence classes were not observed together (threshold ${ELEVATED_GAP_AGGREGATION_THRESHOLD}).`; }
else if (counts.ELEVATED > 0) { priority = "ELEVATED"; rationale = "ELEVATED because one primary evidence class was not observed."; }
else if (counts.NORMAL > 0) { priority = "NORMAL"; rationale = `NORMAL because ${counts.NORMAL} item${counts.NORMAL === 1 ? "" : "s"} ${counts.NORMAL === 1 ? "needs" : "need"} human review or supporting evidence was not observed.`; }
else { priority = "LOW"; rationale = "LOW because every evidence class was observed and none is flagged for review."; }
lines.sort((a, b) => RANK[b.contributes] - RANK[a.contributes] || a.detectorId.localeCompare(b.detectorId));
return { priority, meaning: PRIORITY_MEANING[priority], disclaimer: PRIORITY_DISCLAIMER, rationale, reasons: lines, counts };
}
/** Same rules as family/valueLayer.ts selectContextualActions. */
export function selectContextualActions(result, profile) {
const observed = new Set(result.observed.map((i) => i.id));
const notObserved = new Set(result.notObserved.map((i) => i.id));
const max = profile.maxActions ?? 3;
return profile.actions
.map((a, idx) => ({ a, idx }))
.filter(({ a }) => a.always || a.whenNotObserved?.some((id) => notObserved.has(id)) || a.whenObserved?.some((id) => observed.has(id)))
.sort((x, y) => (y.a.rank ?? 0) - (x.a.rank ?? 0) || x.idx - y.idx)
.map(({ a }) => a)
.slice(0, Math.max(0, max));
}
const NEUTRAL = [
"This is an evidence-organising review, not a verdict.",
"It does not assert safety, certification, approval or compliance.",
"Filename and path detection shows that a document exists where you expect it. It does not read the document and cannot judge its quality.",
"Missing evidence is neutral. Your local policy decides what is sufficient.",
"Re-check before reliance; results reflect the workspace at scan time."
];
export function renderReview(spec, result, profile) {
const p = computeReviewPriority(spec, result, profile);
const actions = selectContextualActions(result, profile);
const observed = new Map(result.observed.map((i) => [i.id, i]));
const review = new Set(result.reviewRequired.map((i) => i.id));
const L = [];
L.push(`# ${spec.displayName}: Evidence Review`, "", `**${profile.question}**`, "", profile.hook, "");
if (result.workspaceName) L.push(`Workspace: **${result.workspaceName}** | Scanned: ${result.scannedAt} | Method: filename and path only`, "");
L.push(`## Review Priority: ${p.priority}`, "", p.meaning, "", `Why: ${p.rationale}`, "");
L.push(...p.reasons.map((r) => `- ${r.label}: ${r.status.toUpperCase().replace("-", " ")} (contributes ${r.contributes}). ${r.detail}`), "");
L.push(`_${p.disclaimer}_`, "");
L.push("## What we observed, what we did not, and why it matters", "");
for (const d of spec.detectors) {
const g = profile.guidance.find((x) => x.detectorId === d.id);
const hit = observed.get(d.id);
const status = hit ? (review.has(d.id) ? "OBSERVED, REVIEW REQUIRED" : "OBSERVED") : "NOT OBSERVED";
L.push(`### ${d.label}: ${status}`, "");
if (hit?.path) L.push(`- Where: \`${hit.path}\``);
if (hit?.detail) L.push(`- Observed: ${hit.detail}`);
if (!hit && d.notObservedDetail) L.push(`- Observed: ${d.notObservedDetail}`);
if (g) {
L.push(`- Why it matters: ${g.whyItMatters}`);
L.push(`- Review next: ${hit ? g.reviewWhenObserved : g.reviewWhenNotObserved}`);
if (g.capability) L.push(`- If you want to go further: ${g.capability.label}. ${g.capability.note} ${g.capability.url}`);
}
L.push("");
}
L.push("## What this means", "", ...result.reasonCodes.map((rc) => `- ${rc.message}`), "");
L.push("## What this does not mean", "", ...NEUTRAL.map((s) => `- ${s}`), "");
L.push("## Next actions for this result", "");
if (actions.length) { actions.forEach((a, i) => { L.push(`${i + 1}. **${a.label}**: ${a.note}`); L.push(` ${a.url}`); }); L.push(""); }
else L.push("_No contextual action for this result._", "");
if (profile.discovery) L.push(`${profile.discovery.label}: ${profile.discovery.url}`, "");
L.push("TrustOps handles setup and checkout. This review runs no payment and creates no ECZ-ID truth, entitlement or Resolver proof.", "");
return L.join("\n");
}
/** JSON projection for machine consumers. */
export function projectReview(spec, result, profile) {
const p = computeReviewPriority(spec, result, profile);
return {
schema_version: "1.0.0",
product: spec.name,
display_name: spec.displayName,
generated_at_utc: result.scannedAt,
method: "filename-and-path-only",
workspace: result.workspaceName,
review_priority: { level: p.priority, meaning: p.meaning, rationale: p.rationale, disclaimer: p.disclaimer, reasons: p.reasons },
observations: [...result.observed.map((i) => ({ ...i, status: result.reviewRequired.some((r) => r.id === i.id) ? "review-required" : "observed" })), ...result.notObserved].sort((a, b) => a.id.localeCompare(b.id)),
public_safe_reason_codes: result.reasonCodes,
contextual_next_actions: selectContextualActions(result, profile).map((a) => ({ id: a.id, label: a.label, url: a.url, kind: a.kind, note: a.note })),
discovery: profile.discovery ?? null,
privacy: { local_first: true, source_upload: false, hidden_telemetry: false, network_during_review: "none" },
do_not_infer: ["safety", "approval", "certification", "compliance", "entitlement", "binding", "current_identity_state"]
};
}
const SPEC = {"name":"eczid-api-trust-plugin","displayName":"ECZ-ID API Trust","purpose":"See which API surfaces you expose, how they are secured, and what has public proof.","detectors":[{"id":"api.contract","label":"API contract (OpenAPI / GraphQL / AsyncAPI)","patterns":["(^|/)openapi\\.(json|ya?ml)$","(^|/)swagger\\.(json|ya?ml)$","\\.graphqls?$","(^|/)schema\\.graphql$","(^|/)asyncapi\\.(json|ya?ml)$","(^|/)api-?spec"],"observedDetail":"An API contract was observed.","notObservedDetail":"No API contract observed.","reviewWhenObserved":true},{"id":"api.auth","label":"Authentication / authorisation configuration","patterns":["oauth","openid","jwks","(^|/)auth(z|n)?/","api-?keys?","(^|/)scopes?\\.(json|ya?ml)$"],"observedDetail":"Authentication or authorisation configuration was observed.","notObservedDetail":"No authentication or authorisation configuration observed.","reviewWhenObserved":true},{"id":"api.catalog","label":"API catalogue / discovery","patterns":["(^|/)\\.well-known/api-catalog$","(^|/)apis?\\.json$","api-?catalog","(^|/)\\.well-known/openapi"],"observedDetail":"An API catalogue or discovery document was observed.","notObservedDetail":"No API catalogue or discovery document observed."},{"id":"api.security","label":"Security policy / disclosure contact","patterns":["(^|/)security\\.md$","(^|/)security\\.txt$","(^|/)\\.well-known/security\\.txt$"],"observedDetail":"A security policy or disclosure contact was observed.","notObservedDetail":"No security policy or disclosure contact observed."},{"id":"api.tests","label":"Contract tests / request collections","patterns":["postman.*\\.json$","\\.http$","insomnia","contract-?tests?","(^|/)pacts?/"],"observedDetail":"Contract tests or request collections were observed.","notObservedDetail":"No contract tests or request collections observed."},{"id":"api.resolverRef","label":"ECZ-ID public proof reference","patterns":["(^|/)\\.well-known/ecz-[a-z0-9-]*\\.json$","(^|/)ecz-api[a-z0-9-]*\\.json$","(^|/)ecz-id[a-z0-9-]*\\.json$"],"observedDetail":"An ECZ-ID public proof reference was observed; check it in Resolver.","notObservedDetail":"No ECZ-ID public proof reference observed. This does not mean unsafe."}]};
const PROFILE = {"question":"Which API surfaces does this workspace expose, how are they secured, and what has public proof?","hook":"ECZ-ID API Trust reviews the API surfaces in a workspace: contracts, catalogues, authentication configuration, disclosure contacts, contract tests and public proof references. Inspection only, filename and path only. It does not test an endpoint or read a secret.","maxActions":3,"guidance":[{"detectorId":"api.contract","whyItMatters":"A contract is the declared surface a consumer, platform or reviewer relies on. Without one, exposure is discovered rather than declared.","reviewWhenObserved":"Check every path and operation is intended, security schemes are declared, and the version matches what is deployed.","reviewWhenNotObserved":"Declare the surface: an OpenAPI, GraphQL or AsyncAPI document is the first artefact a consumer asks for.","weightWhenNotObserved":"high","capability":{"label":"ECZ-ID API Security for VS Code (free)","url":"https://marketplace.visualstudio.com/items?itemName=ecocitizenz.eczid-api-security","note":"Local review of API surfaces and their proof posture."}},{"detectorId":"api.auth","whyItMatters":"Authentication and authorisation configuration is where access is bounded. Reviewers look for declared schemes, scopes and key handling, never values.","reviewWhenObserved":"Confirm each security scheme in the contract has matching configuration, scopes are named, and key material lives outside the repository.","reviewWhenNotObserved":"If the API is not public, record how it is protected; if it is public, declare it in the contract.","weightWhenNotObserved":"elevated"},{"detectorId":"api.catalog","whyItMatters":"A catalogue or discovery document tells machines and reviewers which APIs exist and where their contracts are.","reviewWhenObserved":"Check the catalogue lists the current contract versions.","reviewWhenNotObserved":"Supporting evidence only.","weightWhenNotObserved":"normal"},{"detectorId":"api.security","whyItMatters":"A security policy and disclosure contact are where a reporter, a customer or an authority go first when something is wrong.","reviewWhenObserved":"Confirm the contact is monitored and the policy states the response process.","reviewWhenNotObserved":"Add a SECURITY.md or security.txt with a monitored contact.","weightWhenNotObserved":"normal"},{"detectorId":"api.tests","whyItMatters":"Contract tests and request collections show the declared surface is exercised, which is how drift between contract and deployment is caught.","reviewWhenObserved":"Check the tests cover authentication failures as well as success paths.","reviewWhenNotObserved":"Supporting evidence only.","weightWhenNotObserved":"normal"},{"detectorId":"api.resolverRef","whyItMatters":"An ECZ-ID public proof reference lets a consumer or platform check the API's current public posture in Resolver. Absence is neutral.","reviewWhenObserved":"Run ecz_check_target on the referenced identifier and read the ResultState and ReasonCodes.","reviewWhenNotObserved":"If you operate the API, an ECZ-ID API Passport in TrustOps gives it a resolver-checkable identity.","weightWhenNotObserved":"normal","capability":{"label":"ECZ-ID API Passport (TrustOps)","url":"https://trustops.ecocitizenz.com/start?flow=api-software","note":"Set up in TrustOps. Passport issuance is an ECZ-ID platform service, not a function of this plugin."}}],"actions":[{"id":"api-security-vscode","label":"Free: ECZ-ID API Security for VS Code","url":"https://marketplace.visualstudio.com/items?itemName=ecocitizenz.eczid-api-security","note":"Local review of API surfaces and their proof posture. Free, no account, no telemetry.","kind":"free-tool","whenObserved":["api.contract","api.auth","api.catalog"],"rank":9},{"id":"api-passport-docs","label":"API Passport guidance","url":"https://developers.ecocitizenz.com/agent-trust/api-passport/","note":"How resolvable API identity, authority and evidence work. Developer Gateway, documentation only.","kind":"guidance","always":true,"rank":7},{"id":"api-passport-trustops","label":"ECZ-ID API Passport in TrustOps","url":"https://trustops.ecocitizenz.com/start?flow=api-software","note":"A resolver-checkable identity for an API you operate. TrustOps handles setup and checkout.","kind":"product","whenObserved":["api.contract"],"rank":6},{"id":"trustops-api-flow","label":"API, Software & Repo Trust in TrustOps","url":"https://trustops.ecocitizenz.com/start?flow=api-software","note":"Resolver-verifiable software and API posture.","kind":"product","always":true,"rank":4}],"discovery":{"label":"View all ECZ-ID API and software products","url":"https://developers.ecocitizenz.com/agent-trust/api-passport/"}};
const EXTRA_DOT_ENTRIES = [".well-known"];
const args = process.argv.slice(2);
const wantJson = args.includes("--json");
const root = args.find((a) => !a.startsWith("--")) ?? process.cwd();
const { resolve: resolvePath, basename } = await import("node:path");
const { statSync } = await import("node:fs");
const abs = resolvePath(root);
let st;
try { st = statSync(abs); } catch { console.error("not a directory: " + root); process.exit(2); }
if (!st.isDirectory()) { console.error("not a directory: " + root); process.exit(2); }
const files = listFiles(abs, { extraDotEntries: EXTRA_DOT_ENTRIES });
const result = detectEvidence(SPEC, files, basename(abs));
if (wantJson) console.log(JSON.stringify(projectReview(SPEC, result, PROFILE), null, 2));
else console.log(renderReview(SPEC, result, PROFILE));
SHA-256: 2d067d1aef7533eb33b90aba33e382f3d49368f5db9d59db27948962a4727ba0