mneme-ai 0.34.0 → 0.35.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.
- package/dist/commands/audit.d.ts +22 -0
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.integration.test.js +107 -13
- package/dist/commands/audit.integration.test.js.map +1 -1
- package/dist/commands/audit.js +155 -52
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/audit.smoke.test.d.ts +2 -0
- package/dist/commands/audit.smoke.test.d.ts.map +1 -0
- package/dist/commands/audit.smoke.test.js +171 -0
- package/dist/commands/audit.smoke.test.js.map +1 -0
- package/dist/commands/bot.test.js +15 -6
- package/dist/commands/bot.test.js.map +1 -1
- package/dist/commands/influence.js +4 -1
- package/dist/commands/influence.js.map +1 -1
- package/dist/commands/quant-cli.d.ts.map +1 -1
- package/dist/commands/quant-cli.js +15 -6
- package/dist/commands/quant-cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/commands/audit.d.ts
CHANGED
|
@@ -12,9 +12,31 @@ export interface AuditOptions {
|
|
|
12
12
|
explain?: boolean;
|
|
13
13
|
/** Test seam: inject an enricher factory so unit tests don't hit the network. */
|
|
14
14
|
explainEnricherFactory?: ExplainRequest["enricherFactory"];
|
|
15
|
+
/**
|
|
16
|
+
* --strict: treat `skipped` axes as `fail`. For compliance environments
|
|
17
|
+
* where missing data IS a failure (e.g. no test command defined =
|
|
18
|
+
* cannot ship).
|
|
19
|
+
*/
|
|
20
|
+
strict?: boolean;
|
|
15
21
|
}
|
|
16
22
|
/** Top-level dispatcher. */
|
|
17
23
|
export declare function auditCommand(opts: AuditOptions): Promise<number>;
|
|
24
|
+
/**
|
|
25
|
+
* Render a forensic-grade markdown report.
|
|
26
|
+
*
|
|
27
|
+
* Every axis surfaces:
|
|
28
|
+
* • verdict (pass / warn / fail / skipped)
|
|
29
|
+
* • reason (one-liner)
|
|
30
|
+
* • confidence rating
|
|
31
|
+
* • structured evidence bullets (the FACTS — counts, hashes, paths,
|
|
32
|
+
* deltas — that back the verdict)
|
|
33
|
+
* • caveat (the "ⓘ" line declaring what this axis does NOT check)
|
|
34
|
+
*
|
|
35
|
+
* The headline carries coverage (X/5 axes verified) so the user
|
|
36
|
+
* immediately sees how trustworthy the verdict is. Sniper-accuracy:
|
|
37
|
+
* a "PASS · 2/5 verified · low confidence" report is a yellow flag,
|
|
38
|
+
* not a green light.
|
|
39
|
+
*/
|
|
18
40
|
export declare function renderMarkdownReport(cert: audit.AuditCertificate): string;
|
|
19
41
|
/** Run `git show` for one commit; return unified diff + files touched. */
|
|
20
42
|
export declare function collectDiffForCommit(repoRoot: string, hash: string): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,EAAO,MAAM,gBAAgB,CAAC;AAG5C,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEnE,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IACvE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iFAAiF;IACjF,sBAAsB,CAAC,EAAE,cAAc,CAAC,iBAAiB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,EAAO,MAAM,gBAAgB,CAAC;AAG5C,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEnE,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IACvE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iFAAiF;IACjF,sBAAsB,CAAC,EAAE,cAAc,CAAC,iBAAiB,CAAC,CAAC;IAC3D;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,4BAA4B;AAC5B,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBtE;AAwfD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,KAAK,CAAC,gBAAgB,GAAG,MAAM,CAsHzE;AAID,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,CAuB1C"}
|
|
@@ -12,38 +12,109 @@ function mkCert(over = {}) {
|
|
|
12
12
|
sessionId: "abc1234",
|
|
13
13
|
capturedAt: "2026-05-07T12:00:00Z",
|
|
14
14
|
axes: {
|
|
15
|
-
behavioralParity: {
|
|
16
|
-
|
|
15
|
+
behavioralParity: {
|
|
16
|
+
verdict: "pass",
|
|
17
|
+
reason: "all sample commands match the baseline",
|
|
18
|
+
details: [],
|
|
19
|
+
evidence: [
|
|
20
|
+
{ label: "git_head", value: "exit 0 · 1 line · sha abc12345", ok: true },
|
|
21
|
+
{ label: "node_version", value: "exit 0 · 1 line · sha def67890", ok: true },
|
|
22
|
+
],
|
|
23
|
+
caveat: "Sampling: 3 of 12 commands a real CI would run.",
|
|
24
|
+
confidence: "medium",
|
|
25
|
+
},
|
|
26
|
+
apiContractDrift: {
|
|
27
|
+
verdict: "pass",
|
|
28
|
+
reason: "API surface identical (47 exports, hash matches)",
|
|
29
|
+
details: [],
|
|
30
|
+
evidence: [
|
|
31
|
+
{ label: "exports scanned", value: "47 across 5 package(s)" },
|
|
32
|
+
{ label: "added", value: "0", ok: true },
|
|
33
|
+
{ label: "removed", value: "0", ok: true },
|
|
34
|
+
{ label: "surface hash (after)", value: "sha256:xyz789abc" },
|
|
35
|
+
],
|
|
36
|
+
caveat: "Surface = top-level public exports.",
|
|
37
|
+
confidence: "high",
|
|
38
|
+
},
|
|
17
39
|
testPassRate: {
|
|
18
40
|
verdict: "pass",
|
|
19
41
|
reason: "no new test failures",
|
|
42
|
+
details: [],
|
|
20
43
|
before: "100 passed / 0 failed (5 files)",
|
|
21
44
|
after: "100 passed / 0 failed (5 files)",
|
|
45
|
+
evidence: [
|
|
46
|
+
{ label: "before", value: "100 passed / 0 failed (5 files)" },
|
|
47
|
+
{ label: "after", value: "100 passed / 0 failed (5 files)", ok: true },
|
|
48
|
+
{ label: "delta", value: "+0 passed · +0 failed · +0 file(s)", ok: true },
|
|
49
|
+
],
|
|
50
|
+
confidence: "high",
|
|
22
51
|
},
|
|
23
|
-
perfRegression: {
|
|
24
|
-
|
|
52
|
+
perfRegression: {
|
|
53
|
+
verdict: "pass",
|
|
54
|
+
reason: "no perf regression",
|
|
55
|
+
details: [],
|
|
56
|
+
deltaPercent: 0,
|
|
57
|
+
evidence: [
|
|
58
|
+
{ label: "git_head", value: "baseline 5 ms → current 5 ms (+0%)", ok: true },
|
|
59
|
+
],
|
|
60
|
+
confidence: "medium",
|
|
61
|
+
},
|
|
62
|
+
aiNarrative: {
|
|
63
|
+
verdict: "pass",
|
|
64
|
+
reason: "narrative trust 1.00",
|
|
65
|
+
details: [],
|
|
66
|
+
checks: [],
|
|
67
|
+
evidence: [
|
|
68
|
+
{ label: "AI commits checked", value: "1", ok: true },
|
|
69
|
+
{ label: "claims verified", value: "2", ok: true },
|
|
70
|
+
],
|
|
71
|
+
confidence: "high",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
forensicAxes: {
|
|
75
|
+
size: { verdict: "pass", score: 0.1, reason: "well within median", evidence: [{ label: "score", value: "0.10" }] },
|
|
76
|
+
files: { verdict: "pass", score: 0.1, reason: "all files seen", evidence: [{ label: "score", value: "0.10" }] },
|
|
77
|
+
style: { verdict: "pass", score: 0.1, reason: "verb in vocab", evidence: [{ label: "score", value: "0.10" }] },
|
|
78
|
+
time: { verdict: "pass", score: 0.1, reason: "in window", evidence: [{ label: "score", value: "0.10" }] },
|
|
25
79
|
},
|
|
26
|
-
forensicAxes: { size: "pass", files: "pass", style: "pass", time: "pass" },
|
|
27
80
|
overallVerdict: "pass",
|
|
81
|
+
coverage: { verified: 5, skipped: 0, total: 5, confidence: "high" },
|
|
28
82
|
exitCode: 0,
|
|
29
83
|
...over,
|
|
30
84
|
};
|
|
31
85
|
}
|
|
32
|
-
describe("mneme audit — markdown report", () => {
|
|
86
|
+
describe("mneme audit — markdown report (forensic-grade)", () => {
|
|
33
87
|
it("renders a valid markdown skeleton for a passing certificate", () => {
|
|
34
88
|
const md = renderMarkdownReport(mkCert());
|
|
35
89
|
expect(md).toContain("# AI Audit Trust Certificate");
|
|
36
90
|
expect(md).toContain("`abc1234`");
|
|
37
91
|
expect(md).toContain("PASS");
|
|
38
|
-
|
|
92
|
+
// Headline carries coverage + confidence
|
|
93
|
+
expect(md).toContain("5/5 axes verified");
|
|
94
|
+
expect(md).toContain("high confidence");
|
|
95
|
+
// Verdict table
|
|
96
|
+
expect(md).toContain("| Axis | Verdict | Confidence | Reason |");
|
|
39
97
|
expect(md).toContain("Behavioral parity");
|
|
40
98
|
expect(md).toContain("API contract drift");
|
|
41
99
|
expect(md).toContain("Test pass rate");
|
|
42
100
|
expect(md).toContain("Perf regression");
|
|
43
101
|
expect(md).toContain("AI narrative");
|
|
102
|
+
// Per-axis evidence section
|
|
103
|
+
expect(md).toContain("## Per-Axis Evidence");
|
|
44
104
|
expect(md).toContain("## Forensic Axes");
|
|
45
105
|
});
|
|
46
|
-
it("renders
|
|
106
|
+
it("renders evidence bullets per axis (the FACTS — sniper-grade)", () => {
|
|
107
|
+
const md = renderMarkdownReport(mkCert());
|
|
108
|
+
// Every axis gets evidence bullets in the report
|
|
109
|
+
expect(md).toContain("**git_head**");
|
|
110
|
+
expect(md).toContain("**exports scanned**");
|
|
111
|
+
expect(md).toContain("**before**");
|
|
112
|
+
expect(md).toContain("**after**");
|
|
113
|
+
expect(md).toContain("**AI commits checked**");
|
|
114
|
+
// Caveats surface as italic ⓘ lines
|
|
115
|
+
expect(md).toContain("ⓘ Sampling");
|
|
116
|
+
});
|
|
117
|
+
it("FAIL prominently when overall verdict is fail; reason in the table", () => {
|
|
47
118
|
const cert = mkCert({
|
|
48
119
|
overallVerdict: "fail",
|
|
49
120
|
exitCode: 1,
|
|
@@ -53,6 +124,11 @@ describe("mneme audit — markdown report", () => {
|
|
|
53
124
|
verdict: "fail",
|
|
54
125
|
reason: "1 export(s) removed — silent breaking change",
|
|
55
126
|
details: ["removed: core.foo"],
|
|
127
|
+
evidence: [
|
|
128
|
+
{ label: "removed", value: "1", ok: false },
|
|
129
|
+
{ label: "removed (sample)", value: "core.foo", ok: false },
|
|
130
|
+
],
|
|
131
|
+
confidence: "high",
|
|
56
132
|
},
|
|
57
133
|
},
|
|
58
134
|
});
|
|
@@ -60,6 +136,21 @@ describe("mneme audit — markdown report", () => {
|
|
|
60
136
|
expect(md).toContain("FAIL");
|
|
61
137
|
expect(md).toContain("(exit 1)");
|
|
62
138
|
expect(md).toContain("silent breaking change");
|
|
139
|
+
expect(md).toContain("**removed**");
|
|
140
|
+
});
|
|
141
|
+
it("INSUFFICIENT DATA tripwire surfaces in the header — refuses to certify", () => {
|
|
142
|
+
const cert = mkCert({
|
|
143
|
+
overallVerdict: "warn",
|
|
144
|
+
coverage: { verified: 0, skipped: 5, total: 5, confidence: "low" },
|
|
145
|
+
insufficientData: {
|
|
146
|
+
reason: "no AI-attributed commits AND no measurable change",
|
|
147
|
+
hint: "Capture a baseline, run an AI session, then re-run --certify.",
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
const md = renderMarkdownReport(cert);
|
|
151
|
+
expect(md).toContain("INSUFFICIENT DATA");
|
|
152
|
+
expect(md).toContain("Capture a baseline");
|
|
153
|
+
expect(md).toContain("0/5 axes verified");
|
|
63
154
|
});
|
|
64
155
|
it("includes per-commit narrative checks when present", () => {
|
|
65
156
|
const cert = mkCert({
|
|
@@ -68,6 +159,7 @@ describe("mneme audit — markdown report", () => {
|
|
|
68
159
|
aiNarrative: {
|
|
69
160
|
verdict: "fail",
|
|
70
161
|
reason: "1 commit-message claim(s) contradicted by diff",
|
|
162
|
+
details: [],
|
|
71
163
|
checks: [
|
|
72
164
|
{
|
|
73
165
|
commitHash: "deadbeef0000",
|
|
@@ -83,6 +175,8 @@ describe("mneme audit — markdown report", () => {
|
|
|
83
175
|
narrativeTrustScore: 0,
|
|
84
176
|
},
|
|
85
177
|
],
|
|
178
|
+
evidence: [{ label: "claims contradicted", value: "1", ok: false }],
|
|
179
|
+
confidence: "high",
|
|
86
180
|
},
|
|
87
181
|
},
|
|
88
182
|
overallVerdict: "fail",
|
|
@@ -94,12 +188,12 @@ describe("mneme audit — markdown report", () => {
|
|
|
94
188
|
expect(md).toContain("contradicted");
|
|
95
189
|
expect(md).toContain("no change to db.ts");
|
|
96
190
|
});
|
|
97
|
-
it("forensic axes are listed even when all pass", () => {
|
|
191
|
+
it("forensic axes are listed even when all pass (with reason)", () => {
|
|
98
192
|
const md = renderMarkdownReport(mkCert());
|
|
99
|
-
expect(md).toContain("
|
|
100
|
-
expect(md).toContain("
|
|
101
|
-
expect(md).toContain("
|
|
102
|
-
expect(md).toContain("
|
|
193
|
+
expect(md).toContain("**size**");
|
|
194
|
+
expect(md).toContain("**files**");
|
|
195
|
+
expect(md).toContain("**style**");
|
|
196
|
+
expect(md).toContain("**time**");
|
|
103
197
|
});
|
|
104
198
|
it("exit code is preserved in the report header", () => {
|
|
105
199
|
expect(renderMarkdownReport(mkCert({ exitCode: 0 }))).toContain("(exit 0)");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.integration.test.js","sourceRoot":"","sources":["../../src/commands/audit.integration.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKlD,SAAS,MAAM,CAAC,OAAkC,EAAE;IAClD,OAAO;QACL,SAAS,EAAE,SAAS;QACpB,UAAU,EAAE,sBAAsB;QAClC,IAAI,EAAE;YACJ,gBAAgB,EAAE,EAAE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"audit.integration.test.js","sourceRoot":"","sources":["../../src/commands/audit.integration.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKlD,SAAS,MAAM,CAAC,OAAkC,EAAE;IAClD,OAAO;QACL,SAAS,EAAE,SAAS;QACpB,UAAU,EAAE,sBAAsB;QAClC,IAAI,EAAE;YACJ,gBAAgB,EAAE;gBAChB,OAAO,EAAE,MAAM;gBACf,MAAM,EAAE,wCAAwC;gBAChD,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE;oBACR,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,gCAAgC,EAAE,EAAE,EAAE,IAAI,EAAE;oBACxE,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,gCAAgC,EAAE,EAAE,EAAE,IAAI,EAAE;iBAC7E;gBACD,MAAM,EAAE,iDAAiD;gBACzD,UAAU,EAAE,QAAQ;aACrB;YACD,gBAAgB,EAAE;gBAChB,OAAO,EAAE,MAAM;gBACf,MAAM,EAAE,kDAAkD;gBAC1D,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE;oBACR,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,wBAAwB,EAAE;oBAC7D,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE;oBACxC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE;oBAC1C,EAAE,KAAK,EAAE,sBAAsB,EAAE,KAAK,EAAE,kBAAkB,EAAE;iBAC7D;gBACD,MAAM,EAAE,qCAAqC;gBAC7C,UAAU,EAAE,MAAM;aACnB;YACD,YAAY,EAAE;gBACZ,OAAO,EAAE,MAAM;gBACf,MAAM,EAAE,sBAAsB;gBAC9B,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,iCAAiC;gBACzC,KAAK,EAAE,iCAAiC;gBACxC,QAAQ,EAAE;oBACR,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,iCAAiC,EAAE;oBAC7D,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,iCAAiC,EAAE,EAAE,EAAE,IAAI,EAAE;oBACtE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,oCAAoC,EAAE,EAAE,EAAE,IAAI,EAAE;iBAC1E;gBACD,UAAU,EAAE,MAAM;aACnB;YACD,cAAc,EAAE;gBACd,OAAO,EAAE,MAAM;gBACf,MAAM,EAAE,oBAAoB;gBAC5B,OAAO,EAAE,EAAE;gBACX,YAAY,EAAE,CAAC;gBACf,QAAQ,EAAE;oBACR,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,oCAAoC,EAAE,EAAE,EAAE,IAAI,EAAE;iBAC7E;gBACD,UAAU,EAAE,QAAQ;aACrB;YACD,WAAW,EAAE;gBACX,OAAO,EAAE,MAAM;gBACf,MAAM,EAAE,sBAAsB;gBAC9B,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE;oBACR,EAAE,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE;oBACrD,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE;iBACnD;gBACD,UAAU,EAAE,MAAM;aACnB;SACF;QACD,YAAY,EAAE;YACZ,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE;YAClH,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE;YAC/G,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE;YAC9G,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE;SAC1G;QACD,cAAc,EAAE,MAAM;QACtB,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE;QACnE,QAAQ,EAAE,CAAC;QACX,GAAG,IAAI;KACR,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC9D,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,EAAE,GAAG,oBAAoB,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;QACrD,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAClC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC7B,yCAAyC;QACzC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QACxC,gBAAgB;QAChB,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,0CAA0C,CAAC,CAAC;QACjE,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QAC3C,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QACvC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QACxC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACrC,4BAA4B;QAC5B,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;QAC7C,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,EAAE,GAAG,oBAAoB,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1C,iDAAiD;QACjD,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACrC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;QAC5C,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACnC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAClC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QAC/C,oCAAoC;QACpC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,IAAI,GAAG,MAAM,CAAC;YAClB,cAAc,EAAE,MAAM;YACtB,QAAQ,EAAE,CAAC;YACX,IAAI,EAAE;gBACJ,GAAG,MAAM,EAAE,CAAC,IAAI;gBAChB,gBAAgB,EAAE;oBAChB,OAAO,EAAE,MAAM;oBACf,MAAM,EAAE,8CAA8C;oBACtD,OAAO,EAAE,CAAC,mBAAmB,CAAC;oBAC9B,QAAQ,EAAE;wBACR,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE;wBAC3C,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE;qBAC5D;oBACD,UAAU,EAAE,MAAM;iBACnB;aACF;SACF,CAAC,CAAC;QACH,MAAM,EAAE,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACjC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QAC/C,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QAChF,MAAM,IAAI,GAAG,MAAM,CAAC;YAClB,cAAc,EAAE,MAAM;YACtB,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE;YAClE,gBAAgB,EAAE;gBAChB,MAAM,EAAE,mDAAmD;gBAC3D,IAAI,EAAE,+DAA+D;aACtE;SACF,CAAC,CAAC;QACH,MAAM,EAAE,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QAC3C,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,IAAI,GAAG,MAAM,CAAC;YAClB,IAAI,EAAE;gBACJ,GAAG,MAAM,EAAE,CAAC,IAAI;gBAChB,WAAW,EAAE;oBACX,OAAO,EAAE,MAAM;oBACf,MAAM,EAAE,gDAAgD;oBACxD,OAAO,EAAE,EAAE;oBACX,MAAM,EAAE;wBACN;4BACE,UAAU,EAAE,cAAc;4BAC1B,MAAM,EAAE,EAAE;4BACV,YAAY,EAAE,CAAC,WAAW,CAAC;4BAC3B,aAAa,EAAE;gCACb;oCACE,KAAK,EAAE,oBAAoB;oCAC3B,OAAO,EAAE,cAAc;oCACvB,MAAM,EAAE,oDAAoD;iCAC7D;6BACF;4BACD,mBAAmB,EAAE,CAAC;yBACvB;qBACF;oBACD,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;oBACnE,UAAU,EAAE,MAAM;iBACnB;aACF;YACD,cAAc,EAAE,MAAM;YACtB,QAAQ,EAAE,CAAC;SACZ,CAAC,CAAC;QACH,MAAM,EAAE,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,gCAAgC,CAAC,CAAC;QACvD,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;QACjD,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACrC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,EAAE,GAAG,oBAAoB,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACjC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAClC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAClC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC5E,MAAM,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mCAAmC,EAAE,GAAG,EAAE;IACjD,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QAChC,MAAM,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1D,MAAM,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvD,MAAM,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1D,MAAM,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/commands/audit.js
CHANGED
|
@@ -202,7 +202,7 @@ function renderVerify(trace, checks) {
|
|
|
202
202
|
// ─── --certify ───────────────────────────────────────────────────────
|
|
203
203
|
async function runCertify(opts) {
|
|
204
204
|
const meta = await git.getRepoMeta(opts.cwd);
|
|
205
|
-
const cert = await runFullCertifyPipeline(meta.rootPath);
|
|
205
|
+
const cert = await runFullCertifyPipeline(meta.rootPath, { strict: opts.strict });
|
|
206
206
|
if (!cert) {
|
|
207
207
|
ui.error("No baseline. Run `mneme audit --baseline` first.");
|
|
208
208
|
return 1;
|
|
@@ -261,7 +261,7 @@ function certificateToExplainPrompt(cert) {
|
|
|
261
261
|
};
|
|
262
262
|
return JSON.stringify(compact, null, 2);
|
|
263
263
|
}
|
|
264
|
-
async function runFullCertifyPipeline(repoRoot) {
|
|
264
|
+
async function runFullCertifyPipeline(repoRoot, opts = {}) {
|
|
265
265
|
const baseline = audit.loadBaseline(repoRoot);
|
|
266
266
|
if (!baseline)
|
|
267
267
|
return null;
|
|
@@ -277,6 +277,7 @@ async function runFullCertifyPipeline(repoRoot) {
|
|
|
277
277
|
afterBaseline: after,
|
|
278
278
|
trace,
|
|
279
279
|
diffs,
|
|
280
|
+
strict: opts.strict,
|
|
280
281
|
});
|
|
281
282
|
}
|
|
282
283
|
function renderCertificate(cert, explainSection = null, explainHeadsUp = null) {
|
|
@@ -285,45 +286,80 @@ function renderCertificate(cert, explainSection = null, explainHeadsUp = null) {
|
|
|
285
286
|
: cert.overallVerdict === "warn"
|
|
286
287
|
? pill("WARN", "warn")
|
|
287
288
|
: pill("PASS", "ok");
|
|
289
|
+
// Confidence pill — high (4+ axes verified) / medium (3) / low (≤2).
|
|
290
|
+
const confBadge = (() => {
|
|
291
|
+
switch (cert.coverage.confidence) {
|
|
292
|
+
case "high": return kleur.green("high confidence");
|
|
293
|
+
case "medium": return kleur.yellow("medium confidence");
|
|
294
|
+
case "low": return kleur.red("low confidence");
|
|
295
|
+
}
|
|
296
|
+
})();
|
|
297
|
+
// Headline carries the coverage summary so the user sees verified/skipped
|
|
298
|
+
// up front — never just "PASS · exit 0".
|
|
288
299
|
const headline = (() => {
|
|
300
|
+
if (cert.insufficientData) {
|
|
301
|
+
return `🔍 AI Audit · INSUFFICIENT DATA · refusing to certify`;
|
|
302
|
+
}
|
|
303
|
+
const cov = `${cert.coverage.verified}/${cert.coverage.total} axes verified`;
|
|
304
|
+
const skip = cert.coverage.skipped > 0
|
|
305
|
+
? ` · ${cert.coverage.skipped} skipped (insufficient data)`
|
|
306
|
+
: "";
|
|
289
307
|
if (cert.overallVerdict === "fail") {
|
|
290
|
-
return `🔍 AI Audit · FAIL · review
|
|
308
|
+
return `🔍 AI Audit · FAIL · ${cov}${skip} · review evidence below`;
|
|
291
309
|
}
|
|
292
310
|
if (cert.overallVerdict === "warn") {
|
|
293
|
-
return `🔍 AI Audit ·
|
|
311
|
+
return `🔍 AI Audit · WARN · ${cov}${skip} · review evidence below`;
|
|
294
312
|
}
|
|
295
|
-
return `🔍 AI Audit · PASS ·
|
|
313
|
+
return `🔍 AI Audit · PASS · ${cov}${skip} · ${cert.coverage.confidence} confidence`;
|
|
296
314
|
})();
|
|
297
315
|
const ledeLines = [
|
|
298
|
-
` ${kleur.gray("session:")}
|
|
299
|
-
` ${kleur.gray("captured:")}
|
|
300
|
-
` ${kleur.gray("verdict:")}
|
|
316
|
+
` ${kleur.gray("session:")} ${kleur.bold(cert.sessionId)}`,
|
|
317
|
+
` ${kleur.gray("captured:")} ${cert.capturedAt}`,
|
|
318
|
+
` ${kleur.gray("verdict:")} ${verdictBadge}`,
|
|
319
|
+
` ${kleur.gray("coverage:")} ${kleur.bold(`${cert.coverage.verified}/${cert.coverage.total}`)} axes verified · ${confBadge}`,
|
|
301
320
|
];
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
321
|
+
if (cert.insufficientData) {
|
|
322
|
+
ledeLines.push(` ${kleur.gray("reason:")} ${kleur.yellow(cert.insufficientData.reason)}`, ` ${kleur.gray("hint:")} ${kleur.cyan(cert.insufficientData.hint)}`);
|
|
323
|
+
}
|
|
324
|
+
const mark = (v) => v === "pass" ? kleur.green("✓")
|
|
325
|
+
: v === "warn" ? kleur.yellow("!")
|
|
326
|
+
: v === "skipped" ? kleur.gray("⊘")
|
|
327
|
+
: kleur.red("✗");
|
|
328
|
+
const axisLines = (name, a) => {
|
|
329
|
+
const out = [];
|
|
330
|
+
out.push(` ${mark(a.verdict)} ${kleur.bold(name.padEnd(22))} ${kleur.gray(a.reason)} ${kleur.gray(`[${a.confidence} conf.]`)}`);
|
|
331
|
+
for (const e of a.evidence) {
|
|
332
|
+
const m = e.ok === true ? kleur.green("✓")
|
|
333
|
+
: e.ok === false ? kleur.red("✗")
|
|
334
|
+
: kleur.gray("·");
|
|
335
|
+
out.push(` ${m} ${kleur.gray(e.label.padEnd(22))} ${e.value}`);
|
|
336
|
+
}
|
|
337
|
+
if (a.caveat) {
|
|
338
|
+
out.push(` ${kleur.gray("ⓘ " + a.caveat)}`);
|
|
339
|
+
}
|
|
340
|
+
return out;
|
|
305
341
|
};
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
` ${
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
` ${formatForensic("time", cert.forensicAxes.time)}`,
|
|
318
|
-
];
|
|
319
|
-
const detailLines = [];
|
|
320
|
-
for (const [axis, info] of Object.entries(cert.axes)) {
|
|
321
|
-
const det = info.details;
|
|
322
|
-
if (Array.isArray(det) && det.length > 0) {
|
|
323
|
-
detailLines.push(` ${kleur.bold(axis)}`);
|
|
324
|
-
for (const d of det)
|
|
325
|
-
detailLines.push(` ${kleur.gray(d)}`);
|
|
342
|
+
const evidenceLines = [];
|
|
343
|
+
evidenceLines.push(...axisLines("behavioral parity", cert.axes.behavioralParity));
|
|
344
|
+
evidenceLines.push(...axisLines("API contract drift", cert.axes.apiContractDrift));
|
|
345
|
+
evidenceLines.push(...axisLines("test pass rate", cert.axes.testPassRate));
|
|
346
|
+
evidenceLines.push(...axisLines("perf regression", cert.axes.perfRegression));
|
|
347
|
+
evidenceLines.push(...axisLines("AI narrative", cert.axes.aiNarrative));
|
|
348
|
+
const forensicAxisLines = (name, a) => {
|
|
349
|
+
const out = [];
|
|
350
|
+
out.push(` ${mark(a.verdict)} ${kleur.bold(name.padEnd(8))} ${kleur.gray(a.reason)}`);
|
|
351
|
+
for (const e of a.evidence) {
|
|
352
|
+
out.push(` ${kleur.gray("·")} ${kleur.gray(e.label.padEnd(8))} ${e.value}`);
|
|
326
353
|
}
|
|
354
|
+
return out;
|
|
355
|
+
};
|
|
356
|
+
const forensicLines = [];
|
|
357
|
+
forensicLines.push(...forensicAxisLines("size", cert.forensicAxes.size));
|
|
358
|
+
forensicLines.push(...forensicAxisLines("files", cert.forensicAxes.files));
|
|
359
|
+
forensicLines.push(...forensicAxisLines("style", cert.forensicAxes.style));
|
|
360
|
+
forensicLines.push(...forensicAxisLines("time", cert.forensicAxes.time));
|
|
361
|
+
if (cert.forensicAxes.size.caveat) {
|
|
362
|
+
forensicLines.push(` ${kleur.gray("ⓘ " + cert.forensicAxes.size.caveat)}`);
|
|
327
363
|
}
|
|
328
364
|
const sections = [];
|
|
329
365
|
// --explain narrative goes ABOVE the certificate so a release manager
|
|
@@ -334,16 +370,14 @@ function renderCertificate(cert, explainSection = null, explainHeadsUp = null) {
|
|
|
334
370
|
sections.push({ tier: "lede", lines: [` ${explainHeadsUp}`] });
|
|
335
371
|
}
|
|
336
372
|
sections.push({ tier: "lede", title: "✦ Certificate", lines: ledeLines });
|
|
337
|
-
sections.push({ tier: "key-facts", title: "◆ 5-axis verdict", lines:
|
|
373
|
+
sections.push({ tier: "key-facts", title: "◆ 5-axis verdict (with evidence)", lines: evidenceLines });
|
|
338
374
|
sections.push({ tier: "body", title: "◆ Forensic axes (insider-threat reuse)", lines: forensicLines });
|
|
339
|
-
if (detailLines.length > 0) {
|
|
340
|
-
sections.push({ tier: "details", title: "◆ Per-axis details", lines: detailLines });
|
|
341
|
-
}
|
|
342
375
|
sections.push({
|
|
343
376
|
tier: "sources",
|
|
344
377
|
title: "→ Try next",
|
|
345
378
|
lines: [
|
|
346
379
|
` ${kleur.cyan("$")} ${kleur.bold("mneme audit --report --out audit.md")} ${kleur.gray("(markdown for compliance)")}`,
|
|
380
|
+
` ${kleur.cyan("$")} ${kleur.bold("mneme audit --certify --strict")} ${kleur.gray("(treat skipped axes as fail)")}`,
|
|
347
381
|
` ${kleur.cyan("$")} ${kleur.bold("mneme audit --watch")} ${kleur.gray("(CI gate — re-run automatically)")}`,
|
|
348
382
|
],
|
|
349
383
|
});
|
|
@@ -351,10 +385,6 @@ function renderCertificate(cert, explainSection = null, explainHeadsUp = null) {
|
|
|
351
385
|
process.stdout.write(iris.render({ headline, sections }));
|
|
352
386
|
process.stdout.write("\n");
|
|
353
387
|
}
|
|
354
|
-
function formatForensic(name, v) {
|
|
355
|
-
const mark = v === "pass" ? kleur.green("✓") : v === "warn" ? kleur.yellow("!") : kleur.red("✗");
|
|
356
|
-
return `${mark} ${kleur.gray(name.padEnd(8))} ${v}`;
|
|
357
|
-
}
|
|
358
388
|
// ─── --watch ─────────────────────────────────────────────────────────
|
|
359
389
|
async function runWatch(opts) {
|
|
360
390
|
const meta = await git.getRepoMeta(opts.cwd);
|
|
@@ -393,7 +423,7 @@ async function runWatch(opts) {
|
|
|
393
423
|
// ─── --report ────────────────────────────────────────────────────────
|
|
394
424
|
async function runReport(opts) {
|
|
395
425
|
const meta = await git.getRepoMeta(opts.cwd);
|
|
396
|
-
const cert = await runFullCertifyPipeline(meta.rootPath);
|
|
426
|
+
const cert = await runFullCertifyPipeline(meta.rootPath, { strict: opts.strict });
|
|
397
427
|
if (!cert) {
|
|
398
428
|
ui.error("No baseline. Run `mneme audit --baseline` first.");
|
|
399
429
|
return 1;
|
|
@@ -408,31 +438,104 @@ async function runReport(opts) {
|
|
|
408
438
|
}
|
|
409
439
|
return cert.exitCode;
|
|
410
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Render a forensic-grade markdown report.
|
|
443
|
+
*
|
|
444
|
+
* Every axis surfaces:
|
|
445
|
+
* • verdict (pass / warn / fail / skipped)
|
|
446
|
+
* • reason (one-liner)
|
|
447
|
+
* • confidence rating
|
|
448
|
+
* • structured evidence bullets (the FACTS — counts, hashes, paths,
|
|
449
|
+
* deltas — that back the verdict)
|
|
450
|
+
* • caveat (the "ⓘ" line declaring what this axis does NOT check)
|
|
451
|
+
*
|
|
452
|
+
* The headline carries coverage (X/5 axes verified) so the user
|
|
453
|
+
* immediately sees how trustworthy the verdict is. Sniper-accuracy:
|
|
454
|
+
* a "PASS · 2/5 verified · low confidence" report is a yellow flag,
|
|
455
|
+
* not a green light.
|
|
456
|
+
*/
|
|
411
457
|
export function renderMarkdownReport(cert) {
|
|
412
458
|
const lines = [];
|
|
459
|
+
// ── Header ────────────────────────────────────────────────────────
|
|
413
460
|
lines.push(`# AI Audit Trust Certificate`);
|
|
414
461
|
lines.push(``);
|
|
415
462
|
lines.push(`- **Session**: \`${cert.sessionId}\``);
|
|
416
463
|
lines.push(`- **Captured**: ${cert.capturedAt}`);
|
|
417
|
-
lines.push(`- **Overall verdict**: **${cert.overallVerdict.toUpperCase()}**
|
|
464
|
+
lines.push(`- **Overall verdict**: **${cert.overallVerdict.toUpperCase()}** ` +
|
|
465
|
+
`· ${cert.coverage.verified}/${cert.coverage.total} axes verified` +
|
|
466
|
+
(cert.coverage.skipped > 0 ? ` · ${cert.coverage.skipped} skipped` : "") +
|
|
467
|
+
` · ${cert.coverage.confidence} confidence (exit ${cert.exitCode})`);
|
|
468
|
+
if (cert.insufficientData) {
|
|
469
|
+
lines.push(``);
|
|
470
|
+
lines.push(`> **INSUFFICIENT DATA** — ${cert.insufficientData.reason}`);
|
|
471
|
+
lines.push(`>`);
|
|
472
|
+
lines.push(`> ${cert.insufficientData.hint}`);
|
|
473
|
+
}
|
|
418
474
|
lines.push(``);
|
|
475
|
+
// ── 5-axis verdict (with evidence) ────────────────────────────────
|
|
419
476
|
lines.push(`## 5-Axis Verdict`);
|
|
420
477
|
lines.push(``);
|
|
421
|
-
lines.push(`| Axis | Verdict | Reason |`);
|
|
422
|
-
lines.push(`| ---- | ------- | ------ |`);
|
|
423
|
-
|
|
424
|
-
lines.push(
|
|
425
|
-
lines.push(
|
|
426
|
-
lines.push(
|
|
427
|
-
lines.push(
|
|
478
|
+
lines.push(`| Axis | Verdict | Confidence | Reason |`);
|
|
479
|
+
lines.push(`| ---- | ------- | ---------- | ------ |`);
|
|
480
|
+
const tableRow = (name, a) => `| ${name} | ${a.verdict} | ${a.confidence} | ${a.reason} |`;
|
|
481
|
+
lines.push(tableRow("Behavioral parity", cert.axes.behavioralParity));
|
|
482
|
+
lines.push(tableRow("API contract drift", cert.axes.apiContractDrift));
|
|
483
|
+
lines.push(tableRow("Test pass rate", cert.axes.testPassRate));
|
|
484
|
+
lines.push(tableRow("Perf regression", cert.axes.perfRegression));
|
|
485
|
+
lines.push(tableRow("AI narrative", cert.axes.aiNarrative));
|
|
486
|
+
lines.push(``);
|
|
487
|
+
// ── Per-axis evidence ─────────────────────────────────────────────
|
|
488
|
+
lines.push(`## Per-Axis Evidence`);
|
|
428
489
|
lines.push(``);
|
|
490
|
+
const renderAxis = (name, a) => {
|
|
491
|
+
const icon = a.verdict === "pass" ? "✓"
|
|
492
|
+
: a.verdict === "warn" ? "!"
|
|
493
|
+
: a.verdict === "skipped" ? "⊘"
|
|
494
|
+
: "✗";
|
|
495
|
+
lines.push(`### ${icon} ${name} — \`${a.verdict}\` (${a.confidence} confidence)`);
|
|
496
|
+
lines.push(``);
|
|
497
|
+
lines.push(`> ${a.reason}`);
|
|
498
|
+
lines.push(``);
|
|
499
|
+
if (a.evidence.length > 0) {
|
|
500
|
+
for (const e of a.evidence) {
|
|
501
|
+
const m = e.ok === true ? "✓" : e.ok === false ? "✗" : "·";
|
|
502
|
+
lines.push(`- ${m} **${e.label}** — ${e.value}`);
|
|
503
|
+
}
|
|
504
|
+
lines.push(``);
|
|
505
|
+
}
|
|
506
|
+
if (a.caveat) {
|
|
507
|
+
lines.push(`*ⓘ ${a.caveat}*`);
|
|
508
|
+
lines.push(``);
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
renderAxis("Behavioral parity", cert.axes.behavioralParity);
|
|
512
|
+
renderAxis("API contract drift", cert.axes.apiContractDrift);
|
|
513
|
+
renderAxis("Test pass rate", cert.axes.testPassRate);
|
|
514
|
+
renderAxis("Perf regression", cert.axes.perfRegression);
|
|
515
|
+
renderAxis("AI narrative", cert.axes.aiNarrative);
|
|
516
|
+
// ── Forensic axes ─────────────────────────────────────────────────
|
|
429
517
|
lines.push(`## Forensic Axes`);
|
|
430
518
|
lines.push(``);
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
519
|
+
const renderForensic = (name, a) => {
|
|
520
|
+
const icon = a.verdict === "pass" ? "✓"
|
|
521
|
+
: a.verdict === "warn" ? "!"
|
|
522
|
+
: a.verdict === "skipped" ? "⊘"
|
|
523
|
+
: "✗";
|
|
524
|
+
lines.push(`- ${icon} **${name}** (\`${a.verdict}\`) — ${a.reason}`);
|
|
525
|
+
for (const e of a.evidence) {
|
|
526
|
+
lines.push(` - ${e.label}: ${e.value}`);
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
renderForensic("size", cert.forensicAxes.size);
|
|
530
|
+
renderForensic("files", cert.forensicAxes.files);
|
|
531
|
+
renderForensic("style", cert.forensicAxes.style);
|
|
532
|
+
renderForensic("time", cert.forensicAxes.time);
|
|
533
|
+
if (cert.forensicAxes.size.caveat) {
|
|
534
|
+
lines.push(``);
|
|
535
|
+
lines.push(`*ⓘ ${cert.forensicAxes.size.caveat}*`);
|
|
536
|
+
}
|
|
435
537
|
lines.push(``);
|
|
538
|
+
// ── Per-commit narrative checks (full detail when available) ─────
|
|
436
539
|
if (cert.axes.aiNarrative.checks.length > 0) {
|
|
437
540
|
lines.push(`## Per-Commit Narrative Checks`);
|
|
438
541
|
lines.push(``);
|