verification-layer 0.27.14 → 0.28.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 (64) hide show
  1. package/COMPLIANCE_DELIVERY.md +161 -0
  2. package/dist/baseline.d.ts +50 -12
  3. package/dist/baseline.d.ts.map +1 -1
  4. package/dist/baseline.js +165 -23
  5. package/dist/baseline.js.map +1 -1
  6. package/dist/delivery/diff.d.ts +10 -0
  7. package/dist/delivery/diff.d.ts.map +1 -0
  8. package/dist/delivery/diff.js +41 -0
  9. package/dist/delivery/diff.js.map +1 -0
  10. package/dist/delivery/evidence.d.ts +11 -0
  11. package/dist/delivery/evidence.d.ts.map +1 -0
  12. package/dist/delivery/evidence.js +87 -0
  13. package/dist/delivery/evidence.js.map +1 -0
  14. package/dist/delivery/git.d.ts +8 -0
  15. package/dist/delivery/git.d.ts.map +1 -0
  16. package/dist/delivery/git.js +98 -0
  17. package/dist/delivery/git.js.map +1 -0
  18. package/dist/delivery/governance.d.ts +23 -0
  19. package/dist/delivery/governance.d.ts.map +1 -0
  20. package/dist/delivery/governance.js +118 -0
  21. package/dist/delivery/governance.js.map +1 -0
  22. package/dist/delivery/graph.d.ts +11 -0
  23. package/dist/delivery/graph.d.ts.map +1 -0
  24. package/dist/delivery/graph.js +130 -0
  25. package/dist/delivery/graph.js.map +1 -0
  26. package/dist/delivery/index.d.ts +14 -0
  27. package/dist/delivery/index.d.ts.map +1 -0
  28. package/dist/delivery/index.js +10 -0
  29. package/dist/delivery/index.js.map +1 -0
  30. package/dist/delivery/policy.d.ts +13 -0
  31. package/dist/delivery/policy.d.ts.map +1 -0
  32. package/dist/delivery/policy.js +100 -0
  33. package/dist/delivery/policy.js.map +1 -0
  34. package/dist/delivery/remediation.d.ts +9 -0
  35. package/dist/delivery/remediation.d.ts.map +1 -0
  36. package/dist/delivery/remediation.js +60 -0
  37. package/dist/delivery/remediation.js.map +1 -0
  38. package/dist/delivery/runner.d.ts +26 -0
  39. package/dist/delivery/runner.d.ts.map +1 -0
  40. package/dist/delivery/runner.js +79 -0
  41. package/dist/delivery/runner.js.map +1 -0
  42. package/dist/delivery/types.d.ts +156 -0
  43. package/dist/delivery/types.d.ts.map +1 -0
  44. package/dist/delivery/types.js +2 -0
  45. package/dist/delivery/types.js.map +1 -0
  46. package/dist/delivery/upload.d.ts +20 -0
  47. package/dist/delivery/upload.d.ts.map +1 -0
  48. package/dist/delivery/upload.js +72 -0
  49. package/dist/delivery/upload.js.map +1 -0
  50. package/dist/delivery-cli.d.ts +3 -0
  51. package/dist/delivery-cli.d.ts.map +1 -0
  52. package/dist/delivery-cli.js +306 -0
  53. package/dist/delivery-cli.js.map +1 -0
  54. package/dist/delivery-upload-cli.d.ts +3 -0
  55. package/dist/delivery-upload-cli.d.ts.map +1 -0
  56. package/dist/delivery-upload-cli.js +37 -0
  57. package/dist/delivery-upload-cli.js.map +1 -0
  58. package/dist/index.d.ts +2 -1
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +2 -1
  61. package/dist/index.js.map +1 -1
  62. package/dist/rule-catalog.json +1 -1
  63. package/package.json +11 -4
  64. package/templates/github-actions/compliance-gate.yml +61 -0
@@ -0,0 +1,161 @@
1
+ # vlayer Compliance Delivery
2
+
3
+ `vlayer` can operate as a change-oriented compliance control in addition to a repository scanner. The delivery layer answers a narrower release question:
4
+
5
+ > Did this software change introduce compliance risk that violates our delivery policy?
6
+
7
+ The `vlayer-ci` binary is designed for pull requests and deployment pipelines. It keeps existing baseline debt visible without allowing historical findings to make every future pull request fail.
8
+
9
+ ## 1. Create a portable baseline
10
+
11
+ ```bash
12
+ vlayer baseline . -o .vlayer-baseline.json
13
+ ```
14
+
15
+ Baseline format 2.0 stores project-relative paths and stable semantic fingerprints. Line numbers are retained for display but are not part of the fingerprint, so a line shift or a different CI checkout directory does not invalidate the baseline.
16
+
17
+ Commit `.vlayer-baseline.json` with the repository when you want the current accepted state to be the starting point for change detection.
18
+
19
+ ## 2. Define delivery policy
20
+
21
+ Create `vlayer.policy.json`:
22
+
23
+ ```json
24
+ {
25
+ "blockOn": ["critical", "high"],
26
+ "maxNew": {
27
+ "medium": 3
28
+ },
29
+ "changedFilesOnly": true,
30
+ "minimumScore": 0,
31
+ "requireBaseline": true,
32
+ "failOnExpiredAcknowledgments": true
33
+ }
34
+ ```
35
+
36
+ Policy can also live at `.vlayer/policy.json` or in a `deliveryPolicy` block inside `.vlayerrc.json`. An explicit `--policy` file takes precedence.
37
+
38
+ ### Policy semantics
39
+
40
+ - `blockOn`: any new active finding at one of these severities blocks deployment.
41
+ - `maxNew`: numeric budget for new findings by severity.
42
+ - `changedFilesOnly`: evaluate newly introduced findings only in files changed by the Git comparison when Git metadata is available.
43
+ - `minimumScore`: optional whole-repository compliance score floor.
44
+ - `requireBaseline`: fail when no baseline is available.
45
+ - `failOnExpiredAcknowledgments`: treat expired exceptions as governance failures.
46
+
47
+ Acknowledged and inline-suppressed findings are not considered new active risk. Expired acknowledgments do not suppress findings.
48
+
49
+ ## 3. Gate a pull request
50
+
51
+ ```bash
52
+ vlayer-ci guard . \
53
+ --baseline .vlayer-baseline.json \
54
+ --policy vlayer.policy.json \
55
+ --base main \
56
+ --output vlayer-guard.json
57
+ ```
58
+
59
+ Exit codes:
60
+
61
+ - `0`: policy passed; deployment is allowed.
62
+ - `2`: scan completed but delivery policy blocked the change.
63
+ - `1`: operational failure.
64
+
65
+ AI triage is disabled by default for deterministic CI decisions. Use `--ai` only when a team deliberately wants model-assisted triage in the gate.
66
+
67
+ ## 4. Generate audit evidence
68
+
69
+ ```bash
70
+ vlayer-ci evidence . \
71
+ --baseline .vlayer-baseline.json \
72
+ --policy vlayer.policy.json \
73
+ --base main \
74
+ -o vlayer-evidence.json
75
+ ```
76
+
77
+ The evidence package contains:
78
+
79
+ - source-control provenance (repository, base/head SHA, branch, author when available),
80
+ - delivery decision and policy reasons,
81
+ - new and resolved finding counts,
82
+ - normalized finding metadata,
83
+ - mapped HIPAA controls,
84
+ - a code-to-control compliance graph,
85
+ - a SHA-256 package hash.
86
+
87
+ Evidence deliberately excludes source-code context snippets. This reduces the chance that PHI, credentials, or application secrets are copied into an audit artifact.
88
+
89
+ Verify a stored package:
90
+
91
+ ```bash
92
+ vlayer-ci verify-evidence vlayer-evidence.json
93
+ ```
94
+
95
+ Any modification to the hashed package body causes verification to fail.
96
+
97
+ ## 5. Compliance graph
98
+
99
+ ```bash
100
+ vlayer-ci graph . \
101
+ --baseline .vlayer-baseline.json \
102
+ --base main \
103
+ -o vlayer-compliance-graph.json \
104
+ --mermaid vlayer-compliance-graph.mmd
105
+ ```
106
+
107
+ The graph relates:
108
+
109
+ ```text
110
+ code -> finding -> HIPAA control -> delivery policy
111
+ \-> owner
112
+ evidence ----> finding
113
+ evidence ----> policy decision
114
+ ```
115
+
116
+ This is a machine-readable foundation for workspace reporting, evidence search, and agent reasoning rather than a UI-only diagram.
117
+
118
+ ## 6. Exception lifecycle
119
+
120
+ ```bash
121
+ vlayer-ci exceptions . --config .vlayerrc.json
122
+ ```
123
+
124
+ The command reports active, expired, and soon-to-expire acknowledgments. It exits `2` when expired exceptions exist so an organization can make exception review a scheduled governance control.
125
+
126
+ An acknowledgment should include an owner, reason, date, ticket, and expiry whenever practical. Expiry means re-review, not permanent suppression.
127
+
128
+ ## 7. Compliance agent
129
+
130
+ ```bash
131
+ vlayer-ci agent . --baseline .vlayer-baseline.json --base main
132
+ ```
133
+
134
+ The agent command converts the delivery decision into a remediation plan with file, rule, recommendation, and whether a deterministic vlayer fix exists.
135
+
136
+ To apply only registered deterministic fix strategies and then re-run the gate:
137
+
138
+ ```bash
139
+ vlayer-ci agent . \
140
+ --baseline .vlayer-baseline.json \
141
+ --base main \
142
+ --apply-safe-fixes \
143
+ --markdown vlayer-remediation.md
144
+ ```
145
+
146
+ `--apply-safe-fixes` mutates source files and should therefore be invoked deliberately on a working branch. It never authorizes an AI-generated arbitrary code change.
147
+
148
+ ## GitHub Actions
149
+
150
+ A reusable example is included at `templates/github-actions/compliance-gate.yml`.
151
+
152
+ The important requirements are:
153
+
154
+ 1. Checkout with `fetch-depth: 0` so the base/head comparison is available.
155
+ 2. Run `vlayer-ci guard` before deployment.
156
+ 3. Generate the evidence package even when the gate blocks.
157
+ 4. Upload the guard result and evidence JSON as CI artifacts.
158
+
159
+ ## Security model
160
+
161
+ The delivery layer is a static-analysis and policy-enforcement system, not a certification authority. A passing gate means the configured automated controls found no change that violates the configured policy. It is not a legal attestation that an organization is HIPAA compliant.
@@ -1,12 +1,21 @@
1
1
  import type { Finding } from './types.js';
2
+ export declare const BASELINE_VERSION = "2.0";
2
3
  export interface BaselineEntry {
4
+ /** Stable v2 fingerprint. `hash` is retained for backwards compatibility. */
3
5
  hash: string;
6
+ fingerprint?: string;
7
+ /** Canonical rule family without scanner-generated line suffixes. */
8
+ ruleId?: string;
4
9
  id: string;
10
+ /** Portable path relative to the scanned project root for v2 baselines. */
5
11
  file: string;
12
+ /** Informational only in v2; line number is not part of the fingerprint. */
6
13
  line?: number;
7
14
  title: string;
8
15
  severity: string;
9
16
  category: string;
17
+ /** Normalized matched code used to distinguish repeated rule families. */
18
+ anchor?: string;
10
19
  }
11
20
  export interface Baseline {
12
21
  version: string;
@@ -14,31 +23,60 @@ export interface Baseline {
14
23
  findings: BaselineEntry[];
15
24
  }
16
25
  /**
17
- * Generate a stable hash for a finding
26
+ * Scanner rules historically embedded source line numbers in some finding IDs
27
+ * (for example `enc-des-81`). Those suffixes are location metadata, not rule
28
+ * identity, and therefore must not invalidate a baseline after a refactor.
29
+ * Upper-case catalog IDs such as `CRED-001` and `HIPAA-2026-001` are preserved.
18
30
  */
19
- export declare function generateFindingHash(finding: Finding): string;
31
+ export declare function canonicalRuleId(id: string): string;
20
32
  /**
21
- * Create a baseline entry from a finding
33
+ * Build a stable semantic anchor from the matched source line. Context lines
34
+ * are intentionally whitespace-normalized so formatting-only edits do not
35
+ * create a new baseline finding.
22
36
  */
23
- export declare function createBaselineEntry(finding: Finding): BaselineEntry;
37
+ export declare function getFindingAnchor(finding: Finding): string;
24
38
  /**
25
- * Load baseline from file
39
+ * Infer the checkout root from all real finding paths. This keeps the existing
40
+ * `saveBaseline(path, findings)` API portable without requiring callers to pass
41
+ * the scan root. When a caller knows the root, passing it explicitly is still
42
+ * preferred.
43
+ */
44
+ export declare function inferProjectRoot(findings: Finding[]): string;
45
+ /**
46
+ * Convert a finding path to a portable project-relative path. Virtual project
47
+ * findings retain their stable symbolic names.
48
+ */
49
+ export declare function normalizeFindingFile(finding: Finding, projectRoot?: string): string;
50
+ /**
51
+ * Generate the v2 finding fingerprint. Source line number is deliberately not
52
+ * included. The fingerprint is based on portable file path, canonical rule
53
+ * family, stable title, and matched-code anchor.
54
+ */
55
+ export declare function generateFindingHash(finding: Finding, projectRoot?: string): string;
56
+ /**
57
+ * Create a baseline entry from a finding.
58
+ */
59
+ export declare function createBaselineEntry(finding: Finding, projectRoot?: string): BaselineEntry;
60
+ /**
61
+ * Load baseline from file.
26
62
  */
27
63
  export declare function loadBaseline(path: string): Promise<Baseline | null>;
28
64
  /**
29
- * Save baseline to file
65
+ * Save a portable v2 baseline. `projectRoot` is optional for API compatibility;
66
+ * when omitted it is inferred from the absolute finding paths.
30
67
  */
31
- export declare function saveBaseline(path: string, findings: Finding[]): Promise<void>;
68
+ export declare function saveBaseline(path: string, findings: Finding[], projectRoot?: string): Promise<void>;
32
69
  /**
33
- * Check if a finding exists in the baseline
70
+ * Check if a finding exists in a baseline.
34
71
  */
35
- export declare function isInBaseline(finding: Finding, baseline: Baseline): boolean;
72
+ export declare function isInBaseline(finding: Finding, baseline: Baseline, projectRoot?: string): boolean;
36
73
  /**
37
- * Apply baseline to findings
74
+ * Apply baseline to findings. The root is inferred once for stable v2 hashing,
75
+ * making a committed baseline portable between developer machines and CI.
38
76
  */
39
- export declare function applyBaseline(findings: Finding[], baseline: Baseline | null): Finding[];
77
+ export declare function applyBaseline(findings: Finding[], baseline: Baseline | null, projectRoot?: string): Finding[];
40
78
  /**
41
- * Get statistics about baseline application
79
+ * Get statistics about baseline application.
42
80
  */
43
81
  export declare function getBaselineStats(findings: Finding[]): {
44
82
  total: number;
@@ -1 +1 @@
1
- {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAK5D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAUnE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAQzE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQnF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAG1E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,OAAO,EAAE,CAcvF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,CASA"}
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAQtC,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAMD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAWzD;AAWD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAkB5D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAYnF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAUlF;AAUD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,aAAa,CAczF;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAWzE;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,OAAO,EAAE,EACnB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CASf;AAwBD;;GAEG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAmBT;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,QAAQ,GAAG,IAAI,EACzB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,EAAE,CAaX;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,CASA"}
package/dist/baseline.js CHANGED
@@ -1,35 +1,140 @@
1
1
  import { readFile, writeFile } from 'fs/promises';
2
2
  import { createHash } from 'crypto';
3
+ import { dirname, isAbsolute, relative, resolve, sep } from 'path';
4
+ export const BASELINE_VERSION = '2.0';
5
+ const VIRTUAL_FILES = new Set([
6
+ 'project-level',
7
+ 'ASSET-INVENTORY',
8
+ 'PHI-FLOW-MAP',
9
+ ]);
10
+ function normalizeSlashes(value) {
11
+ return value.replace(/\\/g, '/').replace(/^\.\//, '');
12
+ }
13
+ /**
14
+ * Scanner rules historically embedded source line numbers in some finding IDs
15
+ * (for example `enc-des-81`). Those suffixes are location metadata, not rule
16
+ * identity, and therefore must not invalidate a baseline after a refactor.
17
+ * Upper-case catalog IDs such as `CRED-001` and `HIPAA-2026-001` are preserved.
18
+ */
19
+ export function canonicalRuleId(id) {
20
+ if (/^[a-z][a-z0-9-]*-\d+$/.test(id)) {
21
+ return id.replace(/-\d+$/, '');
22
+ }
23
+ return id;
24
+ }
25
+ /**
26
+ * Build a stable semantic anchor from the matched source line. Context lines
27
+ * are intentionally whitespace-normalized so formatting-only edits do not
28
+ * create a new baseline finding.
29
+ */
30
+ export function getFindingAnchor(finding) {
31
+ if (!finding.context || finding.context.length === 0)
32
+ return '';
33
+ const matched = finding.context.filter(line => line.isMatch);
34
+ const source = matched.length > 0 ? matched : finding.context;
35
+ return source
36
+ .map(line => line.content.trim().toLowerCase().replace(/\s+/g, ' '))
37
+ .filter(Boolean)
38
+ .join('\n')
39
+ .slice(0, 1000);
40
+ }
41
+ function isVirtualFile(file) {
42
+ return VIRTUAL_FILES.has(file);
43
+ }
44
+ function isInside(root, file) {
45
+ const rel = relative(root, file);
46
+ return rel === '' || (!rel.startsWith(`..${sep}`) && rel !== '..' && !isAbsolute(rel));
47
+ }
48
+ /**
49
+ * Infer the checkout root from all real finding paths. This keeps the existing
50
+ * `saveBaseline(path, findings)` API portable without requiring callers to pass
51
+ * the scan root. When a caller knows the root, passing it explicitly is still
52
+ * preferred.
53
+ */
54
+ export function inferProjectRoot(findings) {
55
+ const files = findings
56
+ .map(f => f.file)
57
+ .filter(file => isAbsolute(file) && !isVirtualFile(file))
58
+ .map(file => resolve(file));
59
+ if (files.length === 0)
60
+ return process.cwd();
61
+ let root = dirname(files[0]);
62
+ for (const file of files.slice(1)) {
63
+ while (!isInside(root, file)) {
64
+ const parent = dirname(root);
65
+ if (parent === root)
66
+ break;
67
+ root = parent;
68
+ }
69
+ }
70
+ return root;
71
+ }
3
72
  /**
4
- * Generate a stable hash for a finding
73
+ * Convert a finding path to a portable project-relative path. Virtual project
74
+ * findings retain their stable symbolic names.
5
75
  */
6
- export function generateFindingHash(finding) {
7
- // Use file path, line number, finding ID, and title to create a stable hash
8
- // This allows the same issue at the same location to be matched
76
+ export function normalizeFindingFile(finding, projectRoot) {
77
+ if (isVirtualFile(finding.file))
78
+ return finding.file;
79
+ if (projectRoot && isAbsolute(finding.file)) {
80
+ const root = resolve(projectRoot);
81
+ const file = resolve(finding.file);
82
+ if (isInside(root, file)) {
83
+ return normalizeSlashes(relative(root, file));
84
+ }
85
+ }
86
+ return normalizeSlashes(finding.file);
87
+ }
88
+ /**
89
+ * Generate the v2 finding fingerprint. Source line number is deliberately not
90
+ * included. The fingerprint is based on portable file path, canonical rule
91
+ * family, stable title, and matched-code anchor.
92
+ */
93
+ export function generateFindingHash(finding, projectRoot) {
94
+ const key = [
95
+ 'v2',
96
+ normalizeFindingFile(finding, projectRoot),
97
+ canonicalRuleId(finding.id),
98
+ finding.title.trim().toLowerCase(),
99
+ getFindingAnchor(finding),
100
+ ].join('|');
101
+ return createHash('sha256').update(key).digest('hex').substring(0, 16);
102
+ }
103
+ /**
104
+ * Legacy v1 hash used only to keep exact same-checkout baselines readable.
105
+ */
106
+ function generateLegacyFindingHash(finding) {
9
107
  const key = `${finding.file}:${finding.line || 0}:${finding.id}:${finding.title}`;
10
108
  return createHash('sha256').update(key).digest('hex').substring(0, 16);
11
109
  }
12
110
  /**
13
- * Create a baseline entry from a finding
111
+ * Create a baseline entry from a finding.
14
112
  */
15
- export function createBaselineEntry(finding) {
113
+ export function createBaselineEntry(finding, projectRoot) {
114
+ const fingerprint = generateFindingHash(finding, projectRoot);
16
115
  return {
17
- hash: generateFindingHash(finding),
116
+ hash: fingerprint,
117
+ fingerprint,
118
+ ruleId: canonicalRuleId(finding.id),
18
119
  id: finding.id,
19
- file: finding.file,
120
+ file: normalizeFindingFile(finding, projectRoot),
20
121
  line: finding.line,
21
122
  title: finding.title,
22
123
  severity: finding.severity,
23
124
  category: finding.category,
125
+ anchor: getFindingAnchor(finding) || undefined,
24
126
  };
25
127
  }
26
128
  /**
27
- * Load baseline from file
129
+ * Load baseline from file.
28
130
  */
29
131
  export async function loadBaseline(path) {
30
132
  try {
31
133
  const content = await readFile(path, 'utf-8');
32
134
  const baseline = JSON.parse(content);
135
+ if (!baseline || !Array.isArray(baseline.findings) || typeof baseline.version !== 'string') {
136
+ return null;
137
+ }
33
138
  return baseline;
34
139
  }
35
140
  catch {
@@ -37,32 +142,69 @@ export async function loadBaseline(path) {
37
142
  }
38
143
  }
39
144
  /**
40
- * Save baseline to file
145
+ * Save a portable v2 baseline. `projectRoot` is optional for API compatibility;
146
+ * when omitted it is inferred from the absolute finding paths.
41
147
  */
42
- export async function saveBaseline(path, findings) {
148
+ export async function saveBaseline(path, findings, projectRoot) {
149
+ const root = projectRoot ? resolve(projectRoot) : inferProjectRoot(findings);
43
150
  const baseline = {
44
- version: '1.0',
151
+ version: BASELINE_VERSION,
45
152
  createdAt: new Date().toISOString(),
46
- findings: findings.map(createBaselineEntry),
153
+ findings: findings.map(finding => createBaselineEntry(finding, root)),
47
154
  };
48
155
  await writeFile(path, JSON.stringify(baseline, null, 2), 'utf-8');
49
156
  }
157
+ function pathMatches(entryFile, findingFile) {
158
+ const expected = normalizeSlashes(entryFile);
159
+ const actual = normalizeSlashes(findingFile);
160
+ if (VIRTUAL_FILES.has(expected) || VIRTUAL_FILES.has(actual)) {
161
+ return expected === actual;
162
+ }
163
+ return actual === expected || actual.endsWith(`/${expected}`);
164
+ }
165
+ function semanticEntryMatches(finding, entry) {
166
+ if (!pathMatches(entry.file, finding.file))
167
+ return false;
168
+ if ((entry.ruleId ?? canonicalRuleId(entry.id)) !== canonicalRuleId(finding.id))
169
+ return false;
170
+ if (entry.title.trim().toLowerCase() !== finding.title.trim().toLowerCase())
171
+ return false;
172
+ const anchor = getFindingAnchor(finding);
173
+ if (entry.anchor && anchor)
174
+ return entry.anchor === anchor;
175
+ return true;
176
+ }
50
177
  /**
51
- * Check if a finding exists in the baseline
178
+ * Check if a finding exists in a baseline.
52
179
  */
53
- export function isInBaseline(finding, baseline) {
54
- const hash = generateFindingHash(finding);
55
- return baseline.findings.some(entry => entry.hash === hash);
180
+ export function isInBaseline(finding, baseline, projectRoot) {
181
+ if (baseline.version.startsWith('2')) {
182
+ if (projectRoot) {
183
+ const fingerprint = generateFindingHash(finding, projectRoot);
184
+ if (baseline.findings.some(entry => (entry.fingerprint ?? entry.hash) === fingerprint)) {
185
+ return true;
186
+ }
187
+ }
188
+ // Portable fallback: useful when callers do not know the checkout root.
189
+ return baseline.findings.some(entry => semanticEntryMatches(finding, entry));
190
+ }
191
+ // v1 files remain readable on the checkout where they were generated.
192
+ const legacyHash = generateLegacyFindingHash(finding);
193
+ if (baseline.findings.some(entry => entry.hash === legacyHash))
194
+ return true;
195
+ // Best-effort portability for legacy files while users regenerate to v2.
196
+ return baseline.findings.some(entry => semanticEntryMatches(finding, entry));
56
197
  }
57
198
  /**
58
- * Apply baseline to findings
199
+ * Apply baseline to findings. The root is inferred once for stable v2 hashing,
200
+ * making a committed baseline portable between developer machines and CI.
59
201
  */
60
- export function applyBaseline(findings, baseline) {
61
- if (!baseline) {
202
+ export function applyBaseline(findings, baseline, projectRoot) {
203
+ if (!baseline)
62
204
  return findings;
63
- }
205
+ const root = projectRoot ?? inferProjectRoot(findings);
64
206
  return findings.map(finding => {
65
- if (isInBaseline(finding, baseline)) {
207
+ if (isInBaseline(finding, baseline, root)) {
66
208
  return {
67
209
  ...finding,
68
210
  isBaseline: true,
@@ -72,7 +214,7 @@ export function applyBaseline(findings, baseline) {
72
214
  });
73
215
  }
74
216
  /**
75
- * Get statistics about baseline application
217
+ * Get statistics about baseline application.
76
218
  */
77
219
  export function getBaselineStats(findings) {
78
220
  const baseline = findings.filter(f => f.isBaseline).length;
@@ -1 +1 @@
1
- {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Finding } from '../types.js';
2
+ import type { Baseline } from '../baseline.js';
3
+ import type { FindingDelta } from './types.js';
4
+ /**
5
+ * Build the change-oriented view used by CI policy evaluation. Findings already
6
+ * marked by `scan(..., baselineFile)` are treated as existing debt; active
7
+ * findings outside the baseline are new risk.
8
+ */
9
+ export declare function buildFindingDelta(findings: Finding[], projectRoot: string, changedFiles: string[], baseline: Baseline | null): FindingDelta;
10
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/delivery/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAc/C;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,EAAE,EACnB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EAAE,EACtB,QAAQ,EAAE,QAAQ,GAAG,IAAI,GACxB,YAAY,CA2Bd"}
@@ -0,0 +1,41 @@
1
+ import { generateFindingHash, normalizeFindingFile, } from '../baseline.js';
2
+ const VIRTUAL_FILES = new Set(['project-level', 'ASSET-INVENTORY', 'PHI-FLOW-MAP']);
3
+ function isActive(finding) {
4
+ return !finding.suppressed && !finding.acknowledged;
5
+ }
6
+ function isChangedFile(finding, projectRoot, changed) {
7
+ const file = normalizeFindingFile(finding, projectRoot);
8
+ if (VIRTUAL_FILES.has(file))
9
+ return true;
10
+ return changed.has(file);
11
+ }
12
+ /**
13
+ * Build the change-oriented view used by CI policy evaluation. Findings already
14
+ * marked by `scan(..., baselineFile)` are treated as existing debt; active
15
+ * findings outside the baseline are new risk.
16
+ */
17
+ export function buildFindingDelta(findings, projectRoot, changedFiles, baseline) {
18
+ const changed = new Set(changedFiles.map(file => file.replace(/\\/g, '/')));
19
+ const active = findings.filter(isActive);
20
+ const baselineFindings = active.filter(finding => finding.isBaseline === true);
21
+ const newFindings = active.filter(finding => finding.isBaseline !== true);
22
+ const newFindingsInChangedFiles = changed.size === 0
23
+ ? newFindings
24
+ : newFindings.filter(finding => isChangedFile(finding, projectRoot, changed));
25
+ let resolvedFindings = baseline?.findings.filter(() => false) ?? [];
26
+ if (baseline?.version.startsWith('2')) {
27
+ const currentFingerprints = new Set(findings.map(finding => generateFindingHash(finding, projectRoot)));
28
+ resolvedFindings = baseline.findings.filter(entry => {
29
+ const fingerprint = entry.fingerprint ?? entry.hash;
30
+ return !currentFingerprints.has(fingerprint);
31
+ });
32
+ }
33
+ return {
34
+ newFindings,
35
+ newFindingsInChangedFiles,
36
+ baselineFindings,
37
+ resolvedFindings,
38
+ changedFiles: [...changed],
39
+ };
40
+ }
41
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/delivery/diff.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAGxB,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,eAAe,EAAE,iBAAiB,EAAE,cAAc,CAAC,CAAC,CAAC;AAEpF,SAAS,QAAQ,CAAC,OAAgB;IAChC,OAAO,CAAC,OAAO,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC;AACtD,CAAC;AAED,SAAS,aAAa,CAAC,OAAgB,EAAE,WAAmB,EAAE,OAAoB;IAChF,MAAM,IAAI,GAAG,oBAAoB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACxD,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAmB,EACnB,WAAmB,EACnB,YAAsB,EACtB,QAAyB;IAEzB,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC;IAC/E,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC;IAC1E,MAAM,yBAAyB,GAAG,OAAO,CAAC,IAAI,KAAK,CAAC;QAClD,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IAEhF,IAAI,gBAAgB,GAAG,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACpE,IAAI,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CACjC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CACnE,CAAC;QACF,gBAAgB,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;YAClD,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,CAAC;YACpD,OAAO,CAAC,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,WAAW;QACX,yBAAyB;QACzB,gBAAgB;QAChB,gBAAgB;QAChB,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC;KAC3B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { EvidencePackage, GuardResult } from './types.js';
2
+ export declare function hashEvidencePayload(value: unknown): string;
3
+ /**
4
+ * Create a machine-readable evidence package without embedding source context
5
+ * lines. This intentionally minimizes the risk of copying PHI or secrets into
6
+ * compliance artifacts while preserving rule, file, decision, and provenance.
7
+ */
8
+ export declare function buildEvidencePackage(projectPath: string, guard: GuardResult): EvidencePackage;
9
+ export declare function saveEvidencePackage(path: string, evidence: EvidencePackage): Promise<void>;
10
+ export declare function verifyEvidencePackage(evidence: EvidencePackage): boolean;
11
+ //# sourceMappingURL=evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../src/delivery/evidence.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAmB,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAehF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAG1D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,WAAW,GACjB,eAAe,CAuDjB;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhG;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAGxE"}