@enwithai/product-check 0.2.0 → 0.3.0

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.
Files changed (63) hide show
  1. package/ATTRIBUTION.md +4 -4
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +29 -16
  4. package/SECURITY.md +5 -1
  5. package/dashboard/assurance.css +71 -0
  6. package/dashboard/assurance.js +21 -0
  7. package/dist/assurance/adapters.d.ts +32 -0
  8. package/dist/assurance/adapters.js +76 -0
  9. package/dist/assurance/approvals.d.ts +34 -0
  10. package/dist/assurance/approvals.js +133 -0
  11. package/dist/assurance/cli.d.ts +13 -0
  12. package/dist/assurance/cli.js +863 -0
  13. package/dist/assurance/dashboard.d.ts +18 -0
  14. package/dist/assurance/dashboard.js +267 -0
  15. package/dist/assurance/diff.d.ts +20 -0
  16. package/dist/assurance/diff.js +99 -0
  17. package/dist/assurance/evidence-log.d.ts +20 -0
  18. package/dist/assurance/evidence-log.js +100 -0
  19. package/dist/assurance/fix-packets.d.ts +12 -0
  20. package/dist/assurance/fix-packets.js +178 -0
  21. package/dist/assurance/gate.d.ts +37 -0
  22. package/dist/assurance/gate.js +324 -0
  23. package/dist/assurance/lanes.d.ts +61 -0
  24. package/dist/assurance/lanes.js +380 -0
  25. package/dist/assurance/policy.d.ts +10 -0
  26. package/dist/assurance/policy.js +85 -0
  27. package/dist/assurance/proof.d.ts +35 -0
  28. package/dist/assurance/proof.js +146 -0
  29. package/dist/assurance/register.d.ts +41 -0
  30. package/dist/assurance/register.js +480 -0
  31. package/dist/assurance/render-md.d.ts +17 -0
  32. package/dist/assurance/render-md.js +212 -0
  33. package/dist/assurance/repo-state.d.ts +15 -0
  34. package/dist/assurance/repo-state.js +79 -0
  35. package/dist/assurance/review.d.ts +52 -0
  36. package/dist/assurance/review.js +408 -0
  37. package/dist/assurance/risk.d.ts +21 -0
  38. package/dist/assurance/risk.js +184 -0
  39. package/dist/assurance/types.d.ts +573 -0
  40. package/dist/assurance/types.js +6 -0
  41. package/dist/assurance/util.d.ts +24 -0
  42. package/dist/assurance/util.js +89 -0
  43. package/dist/assurance/verification.d.ts +30 -0
  44. package/dist/assurance/verification.js +212 -0
  45. package/dist/assurance/verify.d.ts +26 -0
  46. package/dist/assurance/verify.js +304 -0
  47. package/dist/cli/run.js +8 -1
  48. package/dist/exec/process.d.ts +27 -0
  49. package/dist/exec/process.js +175 -0
  50. package/dist/reporting/markdown.d.ts +1 -0
  51. package/dist/reporting/markdown.js +1 -1
  52. package/dist/schema/registry.js +9 -0
  53. package/package.json +1 -1
  54. package/schemas/approval.schema.json +98 -0
  55. package/schemas/assurance-common.schema.json +291 -0
  56. package/schemas/assurance-policy.schema.json +185 -0
  57. package/schemas/change-review.schema.json +711 -0
  58. package/schemas/closure-register.schema.json +499 -0
  59. package/schemas/evidence-record.schema.json +94 -0
  60. package/schemas/fix-packet.schema.json +272 -0
  61. package/schemas/release-gate.schema.json +308 -0
  62. package/schemas/reviewer-findings.schema.json +49 -0
  63. package/schemas/verification-run.schema.json +199 -0
@@ -0,0 +1,212 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // SPDX-FileCopyrightText: 2026 EnWithAI Technologies
3
+ /**
4
+ * Markdown renderings of the assurance artifacts: PRODUCT_CHECK_REVIEW.md, PRODUCT_CHECK_BLUEPRINT.md (as-is/to-be
5
+ * Mermaid diagrams, gaps, ordered work packets, tests, gates), PRODUCT_CHECK_RELEASE_GATE.md and human review
6
+ * packets for escalated findings. Every figure comes from the JSON artifacts; untrusted text is escaped.
7
+ */
8
+ import { code, md } from "../reporting/escape.js";
9
+ import { BRANDING } from "../reporting/markdown.js";
10
+ /** Appends the Engineering Commons footer used by every Product Check rendering (omitted with --no-branding). */
11
+ export const withFooter = (text, branding) => branding ? `${text.trimEnd()}\n\n<sub>${BRANDING}</sub>\n` : `${text.trimEnd()}\n`;
12
+ const row = (cells) => `| ${cells.join(" | ")} |`;
13
+ const table = (head, rows) => rows.length === 0
14
+ ? "_None._\n"
15
+ : [row(head), row(head.map(() => "---")), ...rows.map(row)].join("\n") + "\n";
16
+ const header = (title, sub) => `# ${title}\n\n${sub}\n`;
17
+ const note = "_Evidence-bounded: untested areas are unknown, not passed. Not a penetration test, certification or guarantee._\n";
18
+ /** PRODUCT_CHECK_REVIEW.md */
19
+ export function renderReviewMd(review, reg, results) {
20
+ const out = [
21
+ header("Product Check review", `Target ${md(results.contract.target.name)} · methodology CT3 v3.2 · ${note}`),
22
+ ];
23
+ out.push("## Whole product (baseline)\n");
24
+ const s = results.scorecard.pillars;
25
+ out.push(table(["Pillar", "Score", "Coverage", "Confidence"], ["customer", "technology", "trust"].map((p) => [
26
+ p,
27
+ s[p].score === null ? "not scored" : String(s[p].score),
28
+ `${Math.round(s[p].coverage * 100)}%`,
29
+ s[p].confidence,
30
+ ])));
31
+ out.push(`Verdict **${results.verdict.verdict}** at ${results.verdict.tier} (run ${code(results.run.run_id)}). ${md(results.verdict.bounded_by)}\n`);
32
+ if (review) {
33
+ out.push(`## Change review ${code(review.review_id)} (${review.mode})\n`);
34
+ out.push(`Base ${code(review.base_sha?.slice(0, 12) ?? "?")} → head ${code(review.head_sha?.slice(0, 12) ?? "worktree")} · author ${code(review.author.id ?? "unknown")}${review.author.trusted ? " (trusted)" : ""} · cycle ${review.cycle.number}/${review.cycle.max}${review.cycle.escalated ? " · **ESCALATED**" : ""}\n`);
35
+ out.push(`**Gate: ${review.gate.status}.** ${review.gate.reasons.map((r) => md(r)).join(" ")}\n`);
36
+ out.push(`**Risk: ${review.risk.level}** (treated as ${review.risk.effective_level}). ${review.risk.reasons.map((r) => md(r)).join(" ")}\n`);
37
+ out.push(`Approvals required: ${review.approvals_required.humans} human(s)${review.approvals_required.domain_owners.length ? `, domain owners ${review.approvals_required.domain_owners.map((o) => code(o)).join(", ")}` : ""}. Waiver: ${md(review.approvals_required.waiver.reason)}\n`);
38
+ out.push(table(["Approval", "Approver", "Status", "Reason"], review.approvals.map((a) => [code(a.approval_id), code(a.approver_id), a.status, md(a.reason)])));
39
+ out.push(`Checks: **${review.checks.status}** — ${md(review.checks.detail)}\n`);
40
+ out.push("### Lanes\n");
41
+ out.push(table(["Lane", "Status", "Findings", "Notes"], review.lanes.map((l) => [l.lane, l.status, String(l.findings), md(l.notes.join(" "))])));
42
+ out.push("### Findings\n");
43
+ out.push(table(["ID", "Sev", "Verification", "Blocking", "Rule", "Where", "Title"], review.findings.map((f) => [
44
+ code(f.id),
45
+ f.severity,
46
+ f.verification.state,
47
+ f.blocking ? "yes" : "no",
48
+ code(f.rule_id),
49
+ code(`${f.location.path}${f.location.line !== null ? `:${f.location.line}` : ""}`),
50
+ md(f.title),
51
+ ])));
52
+ if (review.disproven.length > 0) {
53
+ out.push("### Disproven (not reported as defects)\n");
54
+ out.push(table(["ID", "Reviewer", "Claim", "Why"], review.disproven.map((f) => [
55
+ code(f.id),
56
+ code(f.reviewer.id),
57
+ md(f.title),
58
+ md(f.verification.checks.map((c) => c.detail).join(" ")),
59
+ ])));
60
+ }
61
+ out.push(`${md(review.product_vs_change)}\n`);
62
+ out.push(`Limitations: ${review.limitations.map((l) => md(l)).join(" ")}\n`);
63
+ }
64
+ else
65
+ out.push("## Change review\n\n_No change review was run (product-check review)._\n");
66
+ if (reg) {
67
+ out.push("## Closure register\n");
68
+ out.push(registerTable(reg.entries));
69
+ }
70
+ return out.join("\n");
71
+ }
72
+ function registerTable(entries) {
73
+ return table(["Finding", "Sev", "State", "Verification", "Cycles", "Control/rule", "Where", "Title"], entries.map((e) => [
74
+ code(e.finding_id),
75
+ e.severity,
76
+ `${e.state}${e.escalated ? " (escalated)" : ""}`,
77
+ e.verification.state,
78
+ String(e.cycles),
79
+ code(e.rule_or_control),
80
+ code(e.location),
81
+ md(e.title),
82
+ ]));
83
+ }
84
+ const mid = (s) => s.replace(/[^A-Za-z0-9_]/g, "_");
85
+ const mlabel = (s) => s
86
+ .replace(/["<>`]/g, "'")
87
+ .replace(/[\r\n]+/g, " ")
88
+ .slice(0, 60);
89
+ /** As-is and to-be Mermaid diagrams. */
90
+ export function mermaidViews(results, packets, reg) {
91
+ const comps = results.blueprint.as_is.components;
92
+ const cats = [...new Set(comps.map((c) => c.category))].sort();
93
+ const lines = (withFixes) => {
94
+ const l = ["flowchart LR"];
95
+ for (const cat of cats) {
96
+ l.push(` subgraph ${mid(`cat_${cat}`)}["${mlabel(cat)}"]`);
97
+ for (const c of comps.filter((x) => x.category === cat))
98
+ l.push(` ${mid(c.id)}["${mlabel(c.name)}${c.certainty === "inferred" ? " (inferred)" : ""}"]`);
99
+ l.push(" end");
100
+ }
101
+ const active = (reg?.entries ?? []).filter((e) => (e.severity === "P0" || e.severity === "P1") && e.state !== "REJECTED_FALSE_POSITIVE");
102
+ for (const e of active.slice(0, 25)) {
103
+ const target = comps.find((c) => e.location.includes(c.name) || c.name.includes(e.location.split(":")[0] ?? "\u0000"));
104
+ const fixed = e.state === "VERIFIED_FIXED" || e.state === "CLOSED";
105
+ if (!withFixes) {
106
+ l.push(` ${mid(`gap_${e.finding_id}`)}{{"${e.finding_id} ${e.severity}: ${mlabel(e.title)}"}}`);
107
+ l.push(` class ${mid(`gap_${e.finding_id}`)} gap`);
108
+ if (target)
109
+ l.push(` ${mid(`gap_${e.finding_id}`)} -.-> ${mid(target.id)}`);
110
+ }
111
+ else {
112
+ const p = packets.find((x) => x.finding_ids.includes(e.finding_id));
113
+ const node = mid(`fix_${e.finding_id}`);
114
+ l.push(` ${node}["${fixed ? "verified: " : "planned: "}${mlabel(p?.proposed_change ?? e.title)}"]`);
115
+ l.push(` class ${node} ${fixed ? "done" : "todo"}`);
116
+ if (target)
117
+ l.push(` ${node} --> ${mid(target.id)}`);
118
+ }
119
+ }
120
+ if (withFixes)
121
+ for (const ch of results.blueprint.to_be.changes.slice(0, 10))
122
+ l.push(` ${mid(ch.id)}(["${mlabel(ch.statement)}"])`);
123
+ l.push(" classDef gap fill:#fde6e6,stroke:#d03b3b", " classDef todo fill:#fff4d6,stroke:#b07d00", " classDef done fill:#e3f6e3,stroke:#0c7a0c");
124
+ return l;
125
+ };
126
+ return { asIs: lines(false).join("\n"), toBe: lines(true).join("\n") };
127
+ }
128
+ /** PRODUCT_CHECK_BLUEPRINT.md */
129
+ export function renderBlueprintMd(results, packets, reg, gate) {
130
+ const v = mermaidViews(results, packets, reg);
131
+ const out = [
132
+ header("Product Check blueprint", `Target ${md(results.contract.target.name)} · scan ${code(results.run.run_id)} · ${note}`),
133
+ ];
134
+ out.push("Blueprint = as-is architecture + gaps + to-be architecture + ordered work packets + tests + release gates. Components marked (inferred) and anything in the uncertainty list are not certain.\n");
135
+ out.push("## As-is architecture and gaps\n", "```mermaid", v.asIs, "```\n");
136
+ out.push("Uncertainties:\n", ...results.blueprint.as_is.uncertainties.map((u) => `- ${md(u)}`), "");
137
+ out.push("## To-be architecture\n", "```mermaid", v.toBe, "```\n");
138
+ out.push(...results.blueprint.to_be.changes.map((c) => `- **${md(c.statement)}** — ${md(c.rationale)}`), "");
139
+ out.push("## Ordered work packets\n");
140
+ const order = { P0: 0, P1: 1, P2: 2, P3: 3 };
141
+ const sorted = [...packets].sort((a, b) => order[a.severity] - order[b.severity] || (a.fix_id < b.fix_id ? -1 : 1));
142
+ out.push(table(["#", "Packet", "Sev", "Risk", "Status", "Proposed change"], sorted.map((p, i) => [
143
+ String(i + 1),
144
+ code(p.fix_id),
145
+ p.severity,
146
+ p.risk_class,
147
+ p.status,
148
+ md(p.proposed_change, 240),
149
+ ])));
150
+ for (const p of sorted) {
151
+ out.push(`### ${code(p.fix_id)} ${md(p.title)}\n`);
152
+ out.push(`- Findings: ${p.finding_ids.map((x) => code(x)).join(", ")} (source ${p.source_finding_ids.map((x) => code(x)).join(", ")})`);
153
+ out.push(`- Where: ${p.traceability.locations.map((x) => code(x)).join(", ") || "—"}`);
154
+ out.push(`- Root cause (${p.root_cause.certainty}, ${p.root_cause.confidence}): ${md(p.root_cause.hypothesis)}`);
155
+ out.push(`- User impact: ${md(p.user_impact)}`);
156
+ out.push(`- Tests: ${p.tests_required.map((t) => `${t.kind} ${code(t.id)}${t.argv ? ` → ${code(t.argv.join(" "))}` : " (command to be declared)"}`).join("; ")}`);
157
+ out.push(`- Negative cases: ${p.negative_cases.map((x) => md(x)).join(" ") || "—"}`);
158
+ out.push(`- Acceptance: ${p.acceptance_conditions.map((x) => md(x)).join(" ")}`);
159
+ out.push(`- Rollback: ${md(p.rollback_plan)}`);
160
+ out.push(`- Scope guard: ${p.scope_guard.allowed_paths.map((x) => code(x)).join(", ")}\n`);
161
+ }
162
+ out.push("## Test strategy\n");
163
+ out.push(table(["Layer", "Current", "Target"], results.blueprint.test_architecture.layers.map((l) => [md(l.layer), l.current, md(l.target)])));
164
+ if (gate) {
165
+ out.push("## Release gates\n");
166
+ out.push(`Decision **${gate.decision}**.\n`);
167
+ out.push(table(["Condition", "Status", "Detail"], gate.conditions.map((c) => [`${c.id} ${md(c.title)}`, c.status, md(c.detail)])));
168
+ }
169
+ return out.join("\n");
170
+ }
171
+ /** PRODUCT_CHECK_RELEASE_GATE.md */
172
+ export function renderGateMd(gate) {
173
+ const out = [
174
+ header(`Release gate: ${gate.decision}`, `Target ${md(gate.target.name)} · head ${code(gate.head_sha?.slice(0, 12) ?? "unknown")} · evaluated ${gate.evaluated_at} · ${note}`),
175
+ ];
176
+ out.push(`Product verdict ${gate.product_verdict} · change gate ${gate.change_gate ?? "no change review"} · open P0 ${gate.open.P0}, P1 ${gate.open.P1}, P2 ${gate.open.P2}, P3 ${gate.open.P3}\n`);
177
+ out.push("## Why\n", ...gate.blocking_reasons.map((r) => `- ${md(r)}`), "");
178
+ out.push("## Release conditions\n");
179
+ out.push(table(["Condition", "Status", "Detail"], gate.conditions.map((c) => [`${c.id} ${md(c.title)}`, c.status, md(c.detail)])));
180
+ out.push("## Executed checks\n");
181
+ out.push(`${gate.executed_checks.count} command execution(s): ${gate.executed_checks.passed} passed, ${gate.executed_checks.failed} failed, ${gate.executed_checks.not_run} not run.\n`);
182
+ out.push("## Accepted risks\n");
183
+ out.push(table(["Acceptance", "Finding", "Expires", "Status"], gate.accepted_risks.map((a) => [code(a.acceptance_id), code(a.finding_id), a.expires, a.status])));
184
+ out.push("## Coverage gaps (disclosed, not passed)\n");
185
+ out.push(...(gate.coverage.gaps.length ? gate.coverage.gaps.map((g) => `- ${md(g)}`) : ["_None recorded._"]), "");
186
+ out.push("## Execution phases (VC-P0…P7; separate from CT-T0…T6 lifecycle tiers)\n");
187
+ out.push(table(["Phase", "Name", "Status", "Evidence"], gate.phases.map((p) => [p.phase, md(p.name), p.status, md(p.evidence.join(" "))])));
188
+ return out.join("\n");
189
+ }
190
+ /** Human review packet for an escalated finding (cycle limit reached). */
191
+ export function renderHumanPacket(e, reg) {
192
+ const runs = reg.verification_runs.filter((r) => e.verification_ids.includes(r.verification_id));
193
+ return [
194
+ header(`Human review needed: ${e.finding_id}`, `${md(e.title)} (${e.severity}, ${code(e.rule_or_control)} at ${code(e.location)})`),
195
+ `Autonomous fix attempts stopped after ${e.cycles} failed verification cycle(s) (limit ${reg.settings.max_cycles}). A person must decide the root cause or design change, then sign \`approve --scope continue:${e.finding_id}\` before another fix attempt, or accept the risk with an owner and expiry.\n`,
196
+ "## Verification history\n",
197
+ table(["Run", "Result", "Commit", "Reasons"], runs.map((r) => [
198
+ code(r.verification_id),
199
+ r.result,
200
+ code(r.exact_sha?.slice(0, 12) ?? "?"),
201
+ md(r.reasons.join(" "), 600),
202
+ ])),
203
+ "## Transitions\n",
204
+ table(["#", "From", "To", "Actor", "Reason"], e.transitions.map((t) => [
205
+ String(t.seq),
206
+ t.from ?? "—",
207
+ t.to,
208
+ `${code(t.actor.id)} (${t.actor.type})`,
209
+ md(t.reason, 300),
210
+ ])),
211
+ ].join("\n");
212
+ }
@@ -0,0 +1,15 @@
1
+ import type { EnvironmentRecord } from "./types.js";
2
+ /** Environment fingerprint for executed evidence. */
3
+ export declare function environmentRecord(repo: string): EnvironmentRecord;
4
+ /** Resolves a revision to a full commit SHA, or null. */
5
+ export declare function resolveSha(repo: string, rev: string): string | null;
6
+ /** Commit, cleanliness and changed paths of the working tree. `ignore` is a directory excluded from the check. */
7
+ export declare function worktreeState(repo: string, ignore: string | null): {
8
+ head: string | null;
9
+ clean: boolean | null;
10
+ dirty: string[];
11
+ };
12
+ /** Commit author email (or name) of a revision. */
13
+ export declare function commitAuthor(repo: string, rev: string): string | null;
14
+ /** File content at a revision, or null when it does not exist there. */
15
+ export declare function fileAt(repo: string, rev: string, path: string): string | null;
@@ -0,0 +1,79 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // SPDX-FileCopyrightText: 2026 EnWithAI Technologies
3
+ /**
4
+ * The exact state a check ran against: commit, whether the worktree was clean, and the environment fingerprint
5
+ * (platform, Node, tool version, lockfile digest). Verification is only meaningful against an exact, clean commit.
6
+ */
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { join, relative, resolve, sep } from "node:path";
9
+ import { gitRead } from "../exec/process.js";
10
+ import { sha256, stableId } from "../util/hash.js";
11
+ import { TOOL_VERSION } from "../version.js";
12
+ import { assertRef } from "./util.js";
13
+ const LOCKFILES = [
14
+ "package-lock.json",
15
+ "pnpm-lock.yaml",
16
+ "yarn.lock",
17
+ "bun.lockb",
18
+ "poetry.lock",
19
+ "uv.lock",
20
+ "Cargo.lock",
21
+ "go.sum",
22
+ ];
23
+ /** Environment fingerprint for executed evidence. */
24
+ export function environmentRecord(repo) {
25
+ const lock = LOCKFILES.map((f) => join(repo, f)).find((p) => existsSync(p));
26
+ const runner = {
27
+ platform: process.platform,
28
+ arch: process.arch,
29
+ node_version: process.version,
30
+ tool_version: TOOL_VERSION,
31
+ };
32
+ const lockfile_digest = lock ? sha256(readFileSync(lock)) : null;
33
+ return {
34
+ environment_id: stableId("ENV", [
35
+ runner.platform,
36
+ runner.arch,
37
+ runner.node_version,
38
+ runner.tool_version,
39
+ lockfile_digest,
40
+ ]),
41
+ runner,
42
+ lockfile_digest,
43
+ };
44
+ }
45
+ /** Resolves a revision to a full commit SHA, or null. */
46
+ export function resolveSha(repo, rev) {
47
+ const r = gitRead(repo, ["rev-parse", "--verify", "--quiet", `${assertRef(rev, "revision")}^{commit}`]);
48
+ const sha = r.stdout.trim();
49
+ return r.ok && /^[0-9a-f]{40,64}$/.test(sha) ? sha : null;
50
+ }
51
+ /** Commit, cleanliness and changed paths of the working tree. `ignore` is a directory excluded from the check. */
52
+ export function worktreeState(repo, ignore) {
53
+ const head = resolveSha(repo, "HEAD");
54
+ const st = gitRead(repo, ["status", "--porcelain=v1", "--untracked-files=normal", "-z"]);
55
+ if (!st.ok)
56
+ return { head, clean: null, dirty: [] };
57
+ const ignoreRel = ignore ? relative(resolve(repo), resolve(ignore)).split(sep).join("/") : null;
58
+ const dirty = st.stdout
59
+ .split("\0")
60
+ .filter((e) => e.length > 3)
61
+ .map((e) => e.slice(3))
62
+ .filter((p) => !(ignoreRel &&
63
+ !ignoreRel.startsWith("..") &&
64
+ (p === ignoreRel || p === `${ignoreRel}/` || p.startsWith(`${ignoreRel}/`))));
65
+ return { head, clean: dirty.length === 0, dirty };
66
+ }
67
+ /** Commit author email (or name) of a revision. */
68
+ export function commitAuthor(repo, rev) {
69
+ const r = gitRead(repo, ["log", "-1", "--format=%ae", assertRef(rev, "revision"), "--"]);
70
+ const v = r.stdout.trim();
71
+ return r.ok && v ? v : null;
72
+ }
73
+ /** File content at a revision, or null when it does not exist there. */
74
+ export function fileAt(repo, rev, path) {
75
+ if (path.startsWith("-") || path.includes("\0"))
76
+ return null;
77
+ const r = gitRead(repo, ["show", `${assertRef(rev, "revision")}:${path}`]);
78
+ return r.ok ? r.stdout : null;
79
+ }
@@ -0,0 +1,52 @@
1
+ import type { Results } from "../types.js";
2
+ import type { DiffFile } from "./diff.js";
3
+ import type { EvidenceLog } from "./evidence-log.js";
4
+ import type { OutcomeInput, SpecInput } from "./adapters.js";
5
+ import { type Approval, type AssurancePolicy, type ChangeReview, type Lane, type VerificationRun } from "./types.js";
6
+ import { type ReviewerClaim } from "./verification.js";
7
+ export interface ReviewInput {
8
+ repo: string;
9
+ targetName: string;
10
+ repositoryLabel: string;
11
+ mode: "PR_DIFF" | "PRE_PR_LOCAL";
12
+ baseSha: string | null;
13
+ headSha: string | null;
14
+ changeRef: string;
15
+ diffText: string;
16
+ diffSource: "git" | "file";
17
+ /** Reads a file as it is at head (commit or worktree); null when absent or unknown. */
18
+ headText: (path: string) => string | null;
19
+ policy: AssurancePolicy;
20
+ policyDigest: string | null;
21
+ author: {
22
+ id: string | null;
23
+ source: "flag" | "git" | "unknown";
24
+ };
25
+ impact: (NonNullable<ChangeReview["risk"]["impact_check"]> & {
26
+ modules: string[];
27
+ }) | null;
28
+ spec: SpecInput | null;
29
+ outcome: OutcomeInput | null;
30
+ claims: ReviewerClaim[];
31
+ previous: ChangeReview | null;
32
+ baseline: {
33
+ results: Results;
34
+ sha256: string;
35
+ } | null;
36
+ proofRun: VerificationRun | null;
37
+ approvals: Approval[];
38
+ log: EvidenceLog;
39
+ now: Date;
40
+ files: DiffFile[];
41
+ }
42
+ /** Dedupe key from rule, component, root-cause signature and impact scope (the signature itself is not stored). */
43
+ export declare const dedupeKey: (h: {
44
+ rule_id: string;
45
+ path: string;
46
+ signature: string;
47
+ lane: Lane;
48
+ }) => string;
49
+ /** Runs a change review. */
50
+ export declare function reviewChange(input: ReviewInput): ChangeReview;
51
+ /** Reads a file from the working tree (PRE_PR_LOCAL); null when missing. */
52
+ export declare function worktreeText(repo: string): (path: string) => string | null;