@codapult/guard 0.6.2 → 0.8.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/README.md +105 -10
- package/dist/adapters/agents/agent-integration.d.ts +3 -0
- package/dist/adapters/agents/agent-integration.js +29 -8
- package/dist/cli/commands/guard.d.ts +13 -0
- package/dist/cli/commands/guard.js +163 -4
- package/dist/cli/index.js +37 -4
- package/dist/core/analysis/doctor.d.ts +1 -1
- package/dist/core/analysis/doctor.js +35 -2
- package/dist/core/audit/governance.d.ts +37 -0
- package/dist/core/audit/governance.js +166 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/guard.d.ts +55 -2
- package/dist/core/guard.js +319 -8
- package/dist/core/history/runs.d.ts +10 -1
- package/dist/core/history/runs.js +18 -2
- package/dist/core/model/types.d.ts +22 -0
- package/dist/core/output/sarif.d.ts +7 -2
- package/dist/core/output/sarif.js +23 -14
- package/dist/core/policy/schemas.d.ts +59 -0
- package/dist/core/policy/schemas.js +37 -0
- package/dist/core/verification/verify.d.ts +6 -2
- package/dist/core/verification/verify.js +114 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/resources.js +18 -1
- package/dist/mcp/tools/guard.js +138 -2
- package/package.json +1 -1
|
@@ -11,6 +11,33 @@ const requiredArtifacts = [
|
|
|
11
11
|
GUARD_CONTRACTS_FILE,
|
|
12
12
|
GUARD_PROPOSALS_FILE,
|
|
13
13
|
];
|
|
14
|
+
function downgradedBoundaryItems(root) {
|
|
15
|
+
let guardConfig;
|
|
16
|
+
try {
|
|
17
|
+
guardConfig = loadGuardConfig(root);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
if (!guardConfig)
|
|
23
|
+
return [];
|
|
24
|
+
const items = guardConfig.rules
|
|
25
|
+
.filter((rule) => rule.kind === 'client-forbidden-import' && rule.severity === 'warning')
|
|
26
|
+
.map((rule) => ({
|
|
27
|
+
path: GUARD_RULES_FILE,
|
|
28
|
+
status: 'warning',
|
|
29
|
+
message: `Boundary rule '${rule.id}' is warning-only and will not block changes.`,
|
|
30
|
+
}));
|
|
31
|
+
items.push(...(guardConfig.contracts ?? [])
|
|
32
|
+
.filter((contract) => (contract.kind === 'import-boundary' || contract.kind === 'package-boundary') &&
|
|
33
|
+
contract.severity === 'warning')
|
|
34
|
+
.map((contract) => ({
|
|
35
|
+
path: GUARD_CONTRACTS_FILE,
|
|
36
|
+
status: 'warning',
|
|
37
|
+
message: `Boundary contract '${contract.id}' is warning-only and will not block changes.`,
|
|
38
|
+
})));
|
|
39
|
+
return items;
|
|
40
|
+
}
|
|
14
41
|
function parseJson(root, path) {
|
|
15
42
|
try {
|
|
16
43
|
return JSON.parse(readFileSync(resolve(root, path), 'utf8'));
|
|
@@ -64,6 +91,7 @@ export function diagnoseGuard(root) {
|
|
|
64
91
|
}
|
|
65
92
|
return { path, status: 'ok', message: 'Artifact is present and readable.' };
|
|
66
93
|
});
|
|
94
|
+
items.push(...downgradedBoundaryItems(root));
|
|
67
95
|
if (initialized && existsSync(resolve(root, GUARD_BASELINE_META_FILE))) {
|
|
68
96
|
const metadata = parseJson(root, GUARD_BASELINE_META_FILE);
|
|
69
97
|
items.push({
|
|
@@ -79,10 +107,11 @@ export function diagnoseGuard(root) {
|
|
|
79
107
|
}
|
|
80
108
|
const missing = items.filter((item) => item.status === 'missing');
|
|
81
109
|
const invalid = items.filter((item) => item.status === 'invalid');
|
|
110
|
+
const warnings = items.filter((item) => item.status === 'warning');
|
|
82
111
|
return {
|
|
83
112
|
status: invalid.length > 0 || (initialized && missing.length > 0)
|
|
84
113
|
? 'fail'
|
|
85
|
-
: missing.length > 0
|
|
114
|
+
: missing.length > 0 || warnings.length > 0
|
|
86
115
|
? 'warning'
|
|
87
116
|
: 'ok',
|
|
88
117
|
initialized,
|
|
@@ -93,6 +122,10 @@ export function diagnoseGuard(root) {
|
|
|
93
122
|
}
|
|
94
123
|
: missing.length > 0
|
|
95
124
|
? { recommendation: 'Run `codapult-guard analyze` or reinitialize Guard intentionally.' }
|
|
96
|
-
:
|
|
125
|
+
: warnings.length > 0
|
|
126
|
+
? {
|
|
127
|
+
recommendation: 'Review downgraded boundary policy before relying on Guard as a hard gate.',
|
|
128
|
+
}
|
|
129
|
+
: {}),
|
|
97
130
|
};
|
|
98
131
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface GuardGovernanceAudit {
|
|
2
|
+
version: 1;
|
|
3
|
+
generatedAt: string;
|
|
4
|
+
configured: boolean;
|
|
5
|
+
policy?: {
|
|
6
|
+
revision?: number | undefined;
|
|
7
|
+
fingerprint: string;
|
|
8
|
+
} | undefined;
|
|
9
|
+
proposals: {
|
|
10
|
+
currentId?: string | undefined;
|
|
11
|
+
currentAuthor?: string | undefined;
|
|
12
|
+
decisions: number;
|
|
13
|
+
authors: string[];
|
|
14
|
+
missingAuthors: number;
|
|
15
|
+
};
|
|
16
|
+
approvals: {
|
|
17
|
+
total: number;
|
|
18
|
+
approved: number;
|
|
19
|
+
rejected: number;
|
|
20
|
+
actors: string[];
|
|
21
|
+
missingActors: number;
|
|
22
|
+
actorAuthorOverlap: string[];
|
|
23
|
+
commitAuthors: string[];
|
|
24
|
+
actorCommitAuthorOverlap: string[];
|
|
25
|
+
missingCommits: number;
|
|
26
|
+
sources: Record<string, number>;
|
|
27
|
+
};
|
|
28
|
+
reachability: {
|
|
29
|
+
approvedWithPolicyFingerprint: number;
|
|
30
|
+
approvedLinkedToRun: number;
|
|
31
|
+
approvedWithoutRun: number;
|
|
32
|
+
runsWithPolicy: number;
|
|
33
|
+
runsWithoutPolicy: number;
|
|
34
|
+
};
|
|
35
|
+
warnings: string[];
|
|
36
|
+
}
|
|
37
|
+
export declare function auditGuardGovernance(root: string): GuardGovernanceAudit;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { createGuardPolicySnapshot, loadBaseline, loadGuardAgentConfig, loadGuardConfig, loadGuardProposals, loadGuardWaivers, withGuardStateLock, GUARD_DIR, } from '../guard.js';
|
|
5
|
+
import { GUARD_RUN_RETENTION, listGuardRuns } from '../history/runs.js';
|
|
6
|
+
function commitAuthor(root, commit, cache) {
|
|
7
|
+
if (!commit || !/^[0-9a-f]{7,64}$/i.test(commit))
|
|
8
|
+
return undefined;
|
|
9
|
+
if (cache.has(commit))
|
|
10
|
+
return cache.get(commit);
|
|
11
|
+
try {
|
|
12
|
+
const value = execFileSync('git', ['show', '-s', '--format=%ae', commit], {
|
|
13
|
+
cwd: root,
|
|
14
|
+
stdio: 'pipe',
|
|
15
|
+
timeout: 5_000,
|
|
16
|
+
maxBuffer: 64 * 1024,
|
|
17
|
+
})
|
|
18
|
+
.toString()
|
|
19
|
+
.trim();
|
|
20
|
+
const author = value || undefined;
|
|
21
|
+
cache.set(commit, author);
|
|
22
|
+
return author;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
cache.set(commit, undefined);
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function currentProposalAuthor(proposals, decision) {
|
|
30
|
+
if (decision.proposalAuthor)
|
|
31
|
+
return decision.proposalAuthor;
|
|
32
|
+
if (decision.proposalId && decision.proposalId === proposals?.proposalId) {
|
|
33
|
+
return proposals.generatedBy;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
function auditGuardGovernanceUnlocked(root) {
|
|
38
|
+
const config = loadGuardConfig(root);
|
|
39
|
+
const proposals = loadGuardProposals(root);
|
|
40
|
+
const decisions = proposals?.decisions ?? [];
|
|
41
|
+
const approved = decisions.filter((decision) => decision.decision === 'approved');
|
|
42
|
+
const authors = [
|
|
43
|
+
...new Set(decisions
|
|
44
|
+
.map((decision) => currentProposalAuthor(proposals, decision))
|
|
45
|
+
.filter((author) => Boolean(author))),
|
|
46
|
+
].sort();
|
|
47
|
+
const actors = [
|
|
48
|
+
...new Set(approved
|
|
49
|
+
.map((decision) => decision.actor?.trim())
|
|
50
|
+
.filter((actor) => Boolean(actor))),
|
|
51
|
+
].sort();
|
|
52
|
+
const commitAuthorCache = new Map();
|
|
53
|
+
const enrichedApprovals = approved.map((decision) => ({
|
|
54
|
+
decision,
|
|
55
|
+
author: currentProposalAuthor(proposals, decision),
|
|
56
|
+
commitAuthor: commitAuthor(root, decision.commit, commitAuthorCache),
|
|
57
|
+
}));
|
|
58
|
+
const commitAuthors = [
|
|
59
|
+
...new Set(enrichedApprovals
|
|
60
|
+
.map((item) => item.commitAuthor)
|
|
61
|
+
.filter((author) => Boolean(author))),
|
|
62
|
+
].sort();
|
|
63
|
+
const actorAuthorOverlap = [
|
|
64
|
+
...new Set(enrichedApprovals
|
|
65
|
+
.filter((item) => item.decision.actor && item.author && item.decision.actor === item.author)
|
|
66
|
+
.map((item) => item.decision.actor)
|
|
67
|
+
.filter((actor) => Boolean(actor))),
|
|
68
|
+
].sort();
|
|
69
|
+
const actorCommitAuthorOverlap = [
|
|
70
|
+
...new Set(enrichedApprovals
|
|
71
|
+
.filter((item) => item.decision.actor && item.commitAuthor === item.decision.actor)
|
|
72
|
+
.map((item) => item.decision.actor)
|
|
73
|
+
.filter((actor) => Boolean(actor))),
|
|
74
|
+
].sort();
|
|
75
|
+
const runs = listGuardRuns(root, GUARD_RUN_RETENTION);
|
|
76
|
+
const runDecisionBindings = new Map();
|
|
77
|
+
for (const run of runs) {
|
|
78
|
+
const fingerprint = run.policy?.fingerprint;
|
|
79
|
+
if (!fingerprint)
|
|
80
|
+
continue;
|
|
81
|
+
for (const decisionId of run.policy?.decisionIds ?? []) {
|
|
82
|
+
const fingerprints = runDecisionBindings.get(decisionId) ?? new Set();
|
|
83
|
+
fingerprints.add(fingerprint);
|
|
84
|
+
runDecisionBindings.set(decisionId, fingerprints);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const approvedWithPolicyFingerprint = approved.filter((decision) => decision.policyFingerprint !== undefined).length;
|
|
88
|
+
const approvedLinkedToRun = approved.filter((decision) => decision.policyFingerprint !== undefined &&
|
|
89
|
+
runDecisionBindings
|
|
90
|
+
.get(decision.decisionId ??
|
|
91
|
+
`${decision.proposalId ?? 'unknown'}:${decision.type}:${decision.id}`)
|
|
92
|
+
?.has(decision.policyFingerprint)).length;
|
|
93
|
+
const warnings = [];
|
|
94
|
+
if (approved.some((decision) => !decision.actor)) {
|
|
95
|
+
warnings.push('Some approved decisions do not declare an approval actor.');
|
|
96
|
+
}
|
|
97
|
+
if (approved.some((decision) => !currentProposalAuthor(proposals, decision))) {
|
|
98
|
+
warnings.push('Some approved decisions do not retain a proposal author.');
|
|
99
|
+
}
|
|
100
|
+
if (actorAuthorOverlap.length > 0) {
|
|
101
|
+
warnings.push('At least one declared approval actor also authored an approved proposal.');
|
|
102
|
+
}
|
|
103
|
+
if (approved.some((decision) => !decision.policyFingerprint)) {
|
|
104
|
+
warnings.push('Some approved decisions are not bound to a resulting policy fingerprint.');
|
|
105
|
+
}
|
|
106
|
+
if (approved.some((decision) => decision.policyFingerprint === undefined ||
|
|
107
|
+
!runDecisionBindings
|
|
108
|
+
.get(decision.decisionId ??
|
|
109
|
+
`${decision.proposalId ?? 'unknown'}:${decision.type}:${decision.id}`)
|
|
110
|
+
?.has(decision.policyFingerprint))) {
|
|
111
|
+
warnings.push('Some approved decisions have no retained verification run linked by decision ID.');
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
version: 1,
|
|
115
|
+
generatedAt: new Date().toISOString(),
|
|
116
|
+
configured: config !== undefined,
|
|
117
|
+
...(config
|
|
118
|
+
? {
|
|
119
|
+
policy: (() => {
|
|
120
|
+
const snapshot = createGuardPolicySnapshot(config, loadBaseline(root), loadGuardWaivers(root), 'working-tree', undefined, loadGuardAgentConfig(root));
|
|
121
|
+
return {
|
|
122
|
+
...(snapshot.revision !== undefined ? { revision: snapshot.revision } : {}),
|
|
123
|
+
fingerprint: snapshot.fingerprint,
|
|
124
|
+
};
|
|
125
|
+
})(),
|
|
126
|
+
}
|
|
127
|
+
: {}),
|
|
128
|
+
proposals: {
|
|
129
|
+
...(proposals?.proposalId ? { currentId: proposals.proposalId } : {}),
|
|
130
|
+
...(proposals?.generatedBy ? { currentAuthor: proposals.generatedBy } : {}),
|
|
131
|
+
decisions: decisions.length,
|
|
132
|
+
authors,
|
|
133
|
+
missingAuthors: decisions.filter((decision) => !currentProposalAuthor(proposals, decision))
|
|
134
|
+
.length,
|
|
135
|
+
},
|
|
136
|
+
approvals: {
|
|
137
|
+
total: decisions.length,
|
|
138
|
+
approved: approved.length,
|
|
139
|
+
rejected: decisions.filter((decision) => decision.decision === 'rejected').length,
|
|
140
|
+
actors,
|
|
141
|
+
missingActors: approved.filter((decision) => !decision.actor).length,
|
|
142
|
+
actorAuthorOverlap,
|
|
143
|
+
commitAuthors,
|
|
144
|
+
actorCommitAuthorOverlap,
|
|
145
|
+
missingCommits: approved.filter((decision) => !decision.commit).length,
|
|
146
|
+
sources: decisions.reduce((counts, decision) => {
|
|
147
|
+
const source = decision.source ?? 'unknown';
|
|
148
|
+
counts[source] = (counts[source] ?? 0) + 1;
|
|
149
|
+
return counts;
|
|
150
|
+
}, {}),
|
|
151
|
+
},
|
|
152
|
+
reachability: {
|
|
153
|
+
approvedWithPolicyFingerprint,
|
|
154
|
+
approvedLinkedToRun,
|
|
155
|
+
approvedWithoutRun: approvedWithPolicyFingerprint - approvedLinkedToRun,
|
|
156
|
+
runsWithPolicy: runs.filter((run) => run.policy !== undefined).length,
|
|
157
|
+
runsWithoutPolicy: runs.filter((run) => run.policy === undefined).length,
|
|
158
|
+
},
|
|
159
|
+
warnings,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
export function auditGuardGovernance(root) {
|
|
163
|
+
if (!existsSync(resolve(root, GUARD_DIR)))
|
|
164
|
+
return auditGuardGovernanceUnlocked(root);
|
|
165
|
+
return withGuardStateLock(root, () => auditGuardGovernanceUnlocked(root));
|
|
166
|
+
}
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type GuardErrorCode = 'GUARD_NOT_CONFIGURED' | 'GUARD_CONFIG_INVALID' | 'GUARD_STATE_BUSY' | 'GUARD_STATE_STALE' | 'GUARD_INVALID_INPUT';
|
|
1
|
+
export type GuardErrorCode = 'GUARD_NOT_CONFIGURED' | 'GUARD_CONFIG_INVALID' | 'GUARD_STATE_BUSY' | 'GUARD_STATE_STALE' | 'GUARD_POLICY_BASE_INVALID' | 'GUARD_POLICY_CHANGED' | 'GUARD_INVALID_INPUT';
|
|
2
2
|
export interface GuardErrorPayload {
|
|
3
3
|
status: 'error';
|
|
4
4
|
outcome: 'not-configured' | 'error';
|
package/dist/core/guard.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type ProjectModel } from './discovery/discovery.js';
|
|
2
2
|
import { clearDiscoveryCache, discoverProject, discoverProjectWithMetrics, findGuardRoot } from './discovery/discovery.js';
|
|
3
3
|
import { type GuardImpactAnalysis } from './analysis/impact.js';
|
|
4
|
-
import type { GuardAdapterName, GuardApprovalMode, GuardContractKind, GuardBudgetMetric, GuardRuleKind, GuardRuleStatus, GuardSeverity, GuardToolMode } from './model/types.js';
|
|
5
|
-
export type { GuardAdapterName, GuardApprovalMode, GuardContractKind, GuardBudgetMetric, GuardRuleKind, GuardRuleStatus, GuardSeverity, GuardToolMode, } from './model/types.js';
|
|
4
|
+
import type { GuardAdapterName, GuardApprovalMode, GuardContractKind, GuardBudgetMetric, GuardRuleKind, GuardRuleStatus, GuardSeverity, GuardToolMode, GuardWaiver, GuardWaiverPolicy } from './model/types.js';
|
|
5
|
+
export type { GuardAdapterName, GuardApprovalMode, GuardContractKind, GuardBudgetMetric, GuardRuleKind, GuardRuleStatus, GuardSeverity, GuardToolMode, GuardWaiver, GuardWaiverPolicy, GuardWaiverDecision, } from './model/types.js';
|
|
6
6
|
export type GuardOutcomeStatus = 'pass' | 'fail' | 'warning' | 'needs-review' | 'not-configured';
|
|
7
7
|
export interface GuardOutcomeInput {
|
|
8
8
|
configured?: boolean | undefined;
|
|
@@ -85,6 +85,7 @@ export interface GuardConfig {
|
|
|
85
85
|
contracts?: GuardContract[] | undefined;
|
|
86
86
|
budgets?: GuardBudget[] | undefined;
|
|
87
87
|
approval?: GuardApprovalPolicy | undefined;
|
|
88
|
+
waiverPolicy?: GuardWaiverPolicy | undefined;
|
|
88
89
|
}
|
|
89
90
|
export interface GuardApprovalPolicy {
|
|
90
91
|
mode: GuardApprovalMode;
|
|
@@ -105,6 +106,7 @@ export interface GuardProposalFile {
|
|
|
105
106
|
decisions?: GuardProposalDecision[] | undefined;
|
|
106
107
|
}
|
|
107
108
|
export interface GuardProposalDecision {
|
|
109
|
+
decisionId?: string | undefined;
|
|
108
110
|
id: string;
|
|
109
111
|
type: 'rule' | 'contract';
|
|
110
112
|
decision: 'approved' | 'rejected';
|
|
@@ -113,8 +115,11 @@ export interface GuardProposalDecision {
|
|
|
113
115
|
proposalFingerprint?: string | undefined;
|
|
114
116
|
revision?: number | undefined;
|
|
115
117
|
source?: 'cli' | 'mcp' | 'external' | undefined;
|
|
118
|
+
proposalAuthor?: string | undefined;
|
|
116
119
|
actor?: string | undefined;
|
|
117
120
|
commit?: string | undefined;
|
|
121
|
+
policyFingerprint?: string | undefined;
|
|
122
|
+
policyRevision?: number | undefined;
|
|
118
123
|
}
|
|
119
124
|
export type GuardProposalFreshness = 'current' | 'stale' | 'unknown';
|
|
120
125
|
export interface GuardAgentConfig {
|
|
@@ -157,12 +162,17 @@ export interface GuardReport {
|
|
|
157
162
|
findings: GuardFinding[];
|
|
158
163
|
suppressed: number;
|
|
159
164
|
scannedFiles: number;
|
|
165
|
+
waived: number;
|
|
166
|
+
waiverWarnings: GuardWaiver[];
|
|
167
|
+
expiredWaivers: GuardWaiver[];
|
|
160
168
|
}
|
|
161
169
|
interface ScanGuardOptions {
|
|
162
170
|
changedOnly?: boolean | undefined;
|
|
163
171
|
changedFiles?: Set<string> | undefined;
|
|
164
172
|
baseline?: Set<string> | undefined;
|
|
165
173
|
includeArchitectureInsights?: boolean | undefined;
|
|
174
|
+
waivers?: GuardWaiver[] | undefined;
|
|
175
|
+
now?: string | undefined;
|
|
166
176
|
}
|
|
167
177
|
export interface GuardReviewPacket {
|
|
168
178
|
version: 1;
|
|
@@ -178,6 +188,9 @@ export interface GuardReviewPacket {
|
|
|
178
188
|
contracts: GuardContract[];
|
|
179
189
|
requirement?: string | undefined;
|
|
180
190
|
deterministicFindings: GuardFinding[];
|
|
191
|
+
waived: number;
|
|
192
|
+
waiverWarnings: GuardWaiver[];
|
|
193
|
+
expiredWaivers: GuardWaiver[];
|
|
181
194
|
impact: GuardImpactAnalysis;
|
|
182
195
|
reviewInstructions: string[];
|
|
183
196
|
}
|
|
@@ -185,6 +198,7 @@ export declare const GUARD_DIR: string;
|
|
|
185
198
|
export declare const GUARD_RULES_FILE: string;
|
|
186
199
|
export declare const GUARD_BASELINE_FILE: string;
|
|
187
200
|
export declare const GUARD_BASELINE_META_FILE: string;
|
|
201
|
+
export declare const GUARD_WAIVERS_FILE: string;
|
|
188
202
|
export declare const GUARD_PROJECT_FILE: string;
|
|
189
203
|
export declare const GUARD_HISTORY_DIR: string;
|
|
190
204
|
export declare const GUARD_ARCHITECTURE_FILE: string;
|
|
@@ -212,6 +226,9 @@ export declare class GuardStateStaleError extends Error {
|
|
|
212
226
|
export declare class GuardBaselineReasonError extends Error {
|
|
213
227
|
constructor();
|
|
214
228
|
}
|
|
229
|
+
export declare class GuardWaiverError extends Error {
|
|
230
|
+
constructor(message: string);
|
|
231
|
+
}
|
|
215
232
|
export declare function withGuardStateLock<T>(root: string, callback: () => T, options?: GuardStateLockOptions): T;
|
|
216
233
|
export declare function isGuardContractsFile(value: unknown): value is {
|
|
217
234
|
version: 1;
|
|
@@ -220,6 +237,30 @@ export declare function isGuardContractsFile(value: unknown): value is {
|
|
|
220
237
|
export declare function isGuardProposalFile(value: unknown): value is GuardProposalFile;
|
|
221
238
|
export declare function fingerprintProjectModel(model: ProjectModel): string;
|
|
222
239
|
export declare function fingerprintGuardConfig(guardConfig: GuardConfig): string;
|
|
240
|
+
export interface GuardPolicySnapshot {
|
|
241
|
+
config: GuardConfig;
|
|
242
|
+
agentConfig: GuardAgentConfig;
|
|
243
|
+
baseline: Set<string>;
|
|
244
|
+
waivers: GuardWaiver[];
|
|
245
|
+
revision?: number | undefined;
|
|
246
|
+
fingerprint: string;
|
|
247
|
+
source: 'working-tree' | 'git-ref';
|
|
248
|
+
ref?: string | undefined;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Fingerprints the effective policy used by a verification run.
|
|
252
|
+
*
|
|
253
|
+
* The rules revision alone is not sufficient: baseline and waiver changes also
|
|
254
|
+
* affect the verdict. Keeping them in this fingerprint makes a run auditable
|
|
255
|
+
* without pretending that a numeric revision covers every policy artifact.
|
|
256
|
+
*/
|
|
257
|
+
export declare function fingerprintGuardPolicy(guardConfig: GuardConfig, baseline?: Set<string>, waivers?: GuardWaiver[], agentConfig?: GuardAgentConfig): string;
|
|
258
|
+
export declare function createGuardPolicySnapshot(guardConfig: GuardConfig, baseline: Set<string>, waivers: GuardWaiver[], source?: GuardPolicySnapshot['source'], ref?: string, agentConfig?: GuardAgentConfig): GuardPolicySnapshot;
|
|
259
|
+
/**
|
|
260
|
+
* Loads policy artifacts from a trusted Git ref instead of the checked-out
|
|
261
|
+
* working tree. This is intended for CI verification of untrusted branches.
|
|
262
|
+
*/
|
|
263
|
+
export declare function loadGuardPolicyAtRevision(root: string, revision: string): GuardPolicySnapshot | undefined;
|
|
223
264
|
export declare function getGuardProposalFreshness(model: ProjectModel, proposals: GuardProposalFile | undefined): GuardProposalFreshness;
|
|
224
265
|
export declare function loadGuardConfig(root: string): GuardConfig | undefined;
|
|
225
266
|
export declare function loadGuardProposals(root: string): GuardProposalFile | undefined;
|
|
@@ -227,6 +268,18 @@ export declare function isGuardAgentConfig(value: unknown): value is GuardAgentC
|
|
|
227
268
|
export declare function loadGuardAgentConfig(root: string): GuardAgentConfig;
|
|
228
269
|
export declare function writeGuardAgentConfig(root: string, agentConfig?: GuardAgentConfig): void;
|
|
229
270
|
export declare function loadBaseline(root: string): Set<string>;
|
|
271
|
+
export interface GuardWaiverUpdateOptions {
|
|
272
|
+
action: 'add' | 'renew' | 'remove';
|
|
273
|
+
waiver?: GuardWaiver | undefined;
|
|
274
|
+
waiverId: string;
|
|
275
|
+
reason: string;
|
|
276
|
+
expiresAt?: string | undefined;
|
|
277
|
+
actor?: string | undefined;
|
|
278
|
+
maxLifetimeDays?: number | undefined;
|
|
279
|
+
}
|
|
280
|
+
export declare function loadGuardWaivers(root: string): GuardWaiver[];
|
|
281
|
+
export declare function writeGuardWaivers(root: string): void;
|
|
282
|
+
export declare function updateGuardWaivers(root: string, options: GuardWaiverUpdateOptions): GuardWaiver[];
|
|
230
283
|
export declare function updateBaseline(root: string, options?: GuardBaselineUpdateOptions): Set<string>;
|
|
231
284
|
export declare function writeGuardConfig(root: string, guardConfig: GuardConfig, options?: GuardConfigWriteOptions): void;
|
|
232
285
|
export declare function writeGuardProposals(root: string, proposals: GuardProposalFile): void;
|