@enwithai/product-check 0.1.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.
- package/ATTRIBUTION.md +6 -6
- package/CHANGELOG.md +66 -0
- package/NOTICE.md +5 -3
- package/README.md +34 -19
- package/SECURITY.md +5 -1
- package/dashboard/assurance.css +71 -0
- package/dashboard/assurance.js +21 -0
- package/dashboard/dashboard.css +6 -0
- package/dist/assurance/adapters.d.ts +32 -0
- package/dist/assurance/adapters.js +76 -0
- package/dist/assurance/approvals.d.ts +34 -0
- package/dist/assurance/approvals.js +133 -0
- package/dist/assurance/cli.d.ts +13 -0
- package/dist/assurance/cli.js +863 -0
- package/dist/assurance/dashboard.d.ts +18 -0
- package/dist/assurance/dashboard.js +267 -0
- package/dist/assurance/diff.d.ts +20 -0
- package/dist/assurance/diff.js +99 -0
- package/dist/assurance/evidence-log.d.ts +20 -0
- package/dist/assurance/evidence-log.js +100 -0
- package/dist/assurance/fix-packets.d.ts +12 -0
- package/dist/assurance/fix-packets.js +178 -0
- package/dist/assurance/gate.d.ts +37 -0
- package/dist/assurance/gate.js +324 -0
- package/dist/assurance/lanes.d.ts +61 -0
- package/dist/assurance/lanes.js +380 -0
- package/dist/assurance/policy.d.ts +10 -0
- package/dist/assurance/policy.js +85 -0
- package/dist/assurance/proof.d.ts +35 -0
- package/dist/assurance/proof.js +146 -0
- package/dist/assurance/register.d.ts +41 -0
- package/dist/assurance/register.js +480 -0
- package/dist/assurance/render-md.d.ts +17 -0
- package/dist/assurance/render-md.js +212 -0
- package/dist/assurance/repo-state.d.ts +15 -0
- package/dist/assurance/repo-state.js +79 -0
- package/dist/assurance/review.d.ts +52 -0
- package/dist/assurance/review.js +408 -0
- package/dist/assurance/risk.d.ts +21 -0
- package/dist/assurance/risk.js +184 -0
- package/dist/assurance/types.d.ts +573 -0
- package/dist/assurance/types.js +6 -0
- package/dist/assurance/util.d.ts +24 -0
- package/dist/assurance/util.js +89 -0
- package/dist/assurance/verification.d.ts +30 -0
- package/dist/assurance/verification.js +212 -0
- package/dist/assurance/verify.d.ts +26 -0
- package/dist/assurance/verify.js +304 -0
- package/dist/blueprint/build.d.ts +3 -1
- package/dist/blueprint/build.js +41 -5
- package/dist/cli/run.js +103 -5
- package/dist/contract/contract.d.ts +4 -0
- package/dist/contract/contract.js +16 -0
- package/dist/dashboard/render.d.ts +5 -0
- package/dist/dashboard/render.js +107 -3
- package/dist/discovery/pops.d.ts +2 -0
- package/dist/discovery/pops.js +7 -5
- package/dist/exec/process.d.ts +27 -0
- package/dist/exec/process.js +175 -0
- package/dist/importers/operational.d.ts +56 -0
- package/dist/importers/operational.js +217 -0
- package/dist/importers/reports.d.ts +24 -0
- package/dist/importers/reports.js +37 -1
- package/dist/inspectors/source/surface.d.ts +30 -0
- package/dist/inspectors/source/surface.js +529 -0
- package/dist/parse/yaml.d.ts +4 -0
- package/dist/parse/yaml.js +39 -0
- package/dist/reporting/markdown.d.ts +1 -0
- package/dist/reporting/markdown.js +18 -1
- package/dist/reporting/spec-markdown.d.ts +9 -0
- package/dist/reporting/spec-markdown.js +144 -0
- package/dist/run.d.ts +2 -0
- package/dist/run.js +52 -2
- package/dist/schema/registry.js +10 -0
- package/dist/scoring/score.js +1 -1
- package/dist/spec/build.d.ts +28 -0
- package/dist/spec/build.js +278 -0
- package/dist/types.d.ts +78 -2
- package/dist/types.js +6 -1
- package/dist/validation/catalog.d.ts +1 -1
- package/dist/validation/catalog.js +125 -2
- package/dist/validation/context.d.ts +6 -1
- package/dist/validation/import-evaluators.js +37 -3
- package/dist/validation/integrity.js +4 -0
- package/dist/validation/operational-evaluators.d.ts +3 -0
- package/dist/validation/operational-evaluators.js +153 -0
- package/dist/validation/source-evaluators.js +1 -1
- package/package.json +4 -1
- package/schemas/approval.schema.json +98 -0
- package/schemas/as-built-spec.schema.json +356 -0
- package/schemas/assurance-common.schema.json +291 -0
- package/schemas/assurance-policy.schema.json +185 -0
- package/schemas/blueprint.schema.json +3 -0
- package/schemas/change-review.schema.json +711 -0
- package/schemas/closure-register.schema.json +499 -0
- package/schemas/evidence-record.schema.json +94 -0
- package/schemas/fix-packet.schema.json +272 -0
- package/schemas/release-gate.schema.json +308 -0
- package/schemas/reviewer-findings.schema.json +49 -0
- package/schemas/scan-contract.schema.json +20 -0
- package/schemas/scorecard.schema.json +1 -1
- package/schemas/verification-run.schema.json +199 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the vibe-code review and verified-closure layer (methodology CT3 v3.2). These artifacts sit next to the
|
|
3
|
+
* results document (schema 0.1.0, unchanged) and are versioned separately: assurance schema 0.1.0. They mirror
|
|
4
|
+
* schemas/{change-review,closure-register,verification-run,fix-packet,approval,release-gate,assurance-policy,
|
|
5
|
+
* evidence-record}.schema.json.
|
|
6
|
+
*/
|
|
7
|
+
import type { Confidence, Epistemic, Pillar, Severity, ValidationStatus } from "../types.js";
|
|
8
|
+
export declare const ASSURANCE_SCHEMA_VERSION: "0.1.0";
|
|
9
|
+
/**
|
|
10
|
+
* Methodology version the assurance design was inspired by (CLEAR CT v3.2, NetworkGain Consulting; internal and
|
|
11
|
+
* unpublished). Relationship `inspired` per the Commons attribution standard: no text or catalog is reproduced.
|
|
12
|
+
*/
|
|
13
|
+
export declare const METHODOLOGY_VERSION: "CT3 v3.2";
|
|
14
|
+
/** Execution phases (mechanics of review and closure). Distinct from CT-T0…T6 lifecycle tiers. */
|
|
15
|
+
export type VcPhase = "VC-P0" | "VC-P1" | "VC-P2" | "VC-P3" | "VC-P4" | "VC-P5" | "VC-P6" | "VC-P7";
|
|
16
|
+
export type ScanModeV32 = "REPO_BASELINE" | "PRE_PR_LOCAL" | "PR_DIFF" | "DEPLOYED_APP" | "HYBRID_360" | "RETEST" | "RELEASE_GATE" | "POST_RELEASE_WATCH";
|
|
17
|
+
/** How far an alleged finding has been verified. Only CONFIRMED/CORROBORATED/REPRODUCED may drive a block. */
|
|
18
|
+
export type VerificationState = "SUSPECTED" | "EVIDENCE_COLLECTED" | "REPRODUCED" | "CORROBORATED" | "CONFIRMED" | "DISPROVEN" | "NEEDS_HUMAN_TRIAGE";
|
|
19
|
+
export type ClosureState = "OPEN" | "ACKNOWLEDGED" | "IN_PROGRESS" | "FIX_PROPOSED" | "RETEST_REQUIRED" | "VERIFIED_FIXED" | "CLOSED" | "REJECTED_FALSE_POSITIVE" | "ACCEPTED_RISK" | "DEFERRED" | "REGRESSED";
|
|
20
|
+
export type RiskLevel = "LOW" | "MEDIUM" | "HIGH" | "UNDETERMINED";
|
|
21
|
+
export type GateStatus = "PASS" | "CONDITIONAL" | "BLOCK" | "NOT_EVALUATED";
|
|
22
|
+
export type ReleaseDecision = "GO" | "CONDITIONAL_GO" | "NO_GO" | "UNKNOWN";
|
|
23
|
+
export type FixStatus = "PROPOSED" | "IMPLEMENTED_UNVERIFIED" | "VERIFIED" | "REGRESSED";
|
|
24
|
+
export type ActorType = "human" | "agent" | "system";
|
|
25
|
+
export interface Actor {
|
|
26
|
+
id: string;
|
|
27
|
+
type: ActorType;
|
|
28
|
+
}
|
|
29
|
+
export interface CommandSpec {
|
|
30
|
+
id: string;
|
|
31
|
+
argv: string[];
|
|
32
|
+
purpose: "build" | "typecheck" | "lint" | "test" | "smoke" | "reproduction" | "regression" | "other";
|
|
33
|
+
timeout_ms: number;
|
|
34
|
+
/** Directory relative to the repository root. */
|
|
35
|
+
cwd: string;
|
|
36
|
+
source: "policy" | "fix" | "corpus";
|
|
37
|
+
}
|
|
38
|
+
export interface PolicyApprover {
|
|
39
|
+
id: string;
|
|
40
|
+
type: "human" | "bot";
|
|
41
|
+
roles: string[];
|
|
42
|
+
/** Ed25519 public key, SPKI PEM. Approvals are verified against it. */
|
|
43
|
+
public_key: string;
|
|
44
|
+
}
|
|
45
|
+
export interface AssurancePolicy {
|
|
46
|
+
schema_version: typeof ASSURANCE_SCHEMA_VERSION;
|
|
47
|
+
kind: "assurance-policy";
|
|
48
|
+
policy_id: string;
|
|
49
|
+
required_checks: CommandSpec[];
|
|
50
|
+
approvers: PolicyApprover[];
|
|
51
|
+
risk: {
|
|
52
|
+
protected_paths: {
|
|
53
|
+
pattern: string;
|
|
54
|
+
owners: string[];
|
|
55
|
+
}[];
|
|
56
|
+
high_risk_paths: string[];
|
|
57
|
+
low_risk_paths: string[];
|
|
58
|
+
trusted_authors: string[];
|
|
59
|
+
allow_low_risk_waiver: boolean;
|
|
60
|
+
approvals: Record<"LOW" | "MEDIUM" | "HIGH", number>;
|
|
61
|
+
};
|
|
62
|
+
review: {
|
|
63
|
+
max_cycles: number;
|
|
64
|
+
fail_closed_unverified_p0: boolean;
|
|
65
|
+
};
|
|
66
|
+
release: {
|
|
67
|
+
rollback_plan: string | null;
|
|
68
|
+
staged_rollout: string | null;
|
|
69
|
+
};
|
|
70
|
+
exec: {
|
|
71
|
+
env_allowlist: string[];
|
|
72
|
+
max_output_bytes: number;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export interface RunnerInfo {
|
|
76
|
+
platform: string;
|
|
77
|
+
arch: string;
|
|
78
|
+
node_version: string;
|
|
79
|
+
tool_version: string;
|
|
80
|
+
}
|
|
81
|
+
export interface EnvironmentRecord {
|
|
82
|
+
environment_id: string;
|
|
83
|
+
runner: RunnerInfo;
|
|
84
|
+
lockfile_digest: string | null;
|
|
85
|
+
}
|
|
86
|
+
export interface CommandExecution {
|
|
87
|
+
execution_id: string;
|
|
88
|
+
command_id: string;
|
|
89
|
+
purpose: CommandSpec["purpose"];
|
|
90
|
+
argv: string[];
|
|
91
|
+
cwd: string;
|
|
92
|
+
status: ValidationStatus;
|
|
93
|
+
exit_code: number | null;
|
|
94
|
+
signal: string | null;
|
|
95
|
+
duration_ms: number;
|
|
96
|
+
started_at: string;
|
|
97
|
+
environment_id: string;
|
|
98
|
+
commit_sha: string | null;
|
|
99
|
+
stdout_digest: string | null;
|
|
100
|
+
stderr_digest: string | null;
|
|
101
|
+
output_bytes: number;
|
|
102
|
+
output_truncated: boolean;
|
|
103
|
+
/** Redacted tail of the combined output (at most 400 characters). */
|
|
104
|
+
excerpt: string | null;
|
|
105
|
+
reason: string | null;
|
|
106
|
+
evidence_id: string | null;
|
|
107
|
+
}
|
|
108
|
+
/** One line of review-evidence.jsonl: append-only and hash-chained. */
|
|
109
|
+
export interface EvidenceRecord {
|
|
110
|
+
schema_version: typeof ASSURANCE_SCHEMA_VERSION;
|
|
111
|
+
kind: "evidence-record";
|
|
112
|
+
seq: number;
|
|
113
|
+
evidence_id: string;
|
|
114
|
+
recorded_at: string;
|
|
115
|
+
type: "command-execution" | "diff-hunk" | "source-recheck" | "results-artifact" | "approval" | "verification" | "imported-report" | "reviewer-claim" | "transition";
|
|
116
|
+
subject: string;
|
|
117
|
+
commit_sha: string | null;
|
|
118
|
+
environment_id: string | null;
|
|
119
|
+
summary: string;
|
|
120
|
+
locator: string | null;
|
|
121
|
+
line: number | null;
|
|
122
|
+
payload_digest: string;
|
|
123
|
+
payload: Record<string, unknown>;
|
|
124
|
+
prev_digest: string | null;
|
|
125
|
+
digest: string;
|
|
126
|
+
}
|
|
127
|
+
export type Lane = "spec-outcome" | "correctness" | "impact" | "trust-security" | "experience" | "operations";
|
|
128
|
+
export interface VerificationCheck {
|
|
129
|
+
check: string;
|
|
130
|
+
result: "pass" | "fail" | "not-run";
|
|
131
|
+
detail: string;
|
|
132
|
+
}
|
|
133
|
+
export interface FindingVerification {
|
|
134
|
+
state: VerificationState;
|
|
135
|
+
checks: VerificationCheck[];
|
|
136
|
+
}
|
|
137
|
+
export interface ReviewFinding {
|
|
138
|
+
id: string;
|
|
139
|
+
rule_id: string;
|
|
140
|
+
lane: Lane;
|
|
141
|
+
reviewer: {
|
|
142
|
+
id: string;
|
|
143
|
+
type: "deterministic" | "ai" | "human" | "imported";
|
|
144
|
+
version: string;
|
|
145
|
+
};
|
|
146
|
+
title: string;
|
|
147
|
+
severity: Severity;
|
|
148
|
+
pillars: Pillar[];
|
|
149
|
+
location: {
|
|
150
|
+
path: string;
|
|
151
|
+
line: number | null;
|
|
152
|
+
runtime: string | null;
|
|
153
|
+
};
|
|
154
|
+
expected: string;
|
|
155
|
+
actual: string;
|
|
156
|
+
steps_or_command: string[];
|
|
157
|
+
evidence_ids: string[];
|
|
158
|
+
confidence: Confidence;
|
|
159
|
+
epistemic: Epistemic;
|
|
160
|
+
impact: string;
|
|
161
|
+
affected_scope: string;
|
|
162
|
+
baseline_sha: string | null;
|
|
163
|
+
head_sha: string | null;
|
|
164
|
+
uncertainty: string | null;
|
|
165
|
+
verification: FindingVerification;
|
|
166
|
+
dedupe_key: string;
|
|
167
|
+
occurrences: number;
|
|
168
|
+
repeat_of_review: string | null;
|
|
169
|
+
blocking: boolean;
|
|
170
|
+
}
|
|
171
|
+
export interface ChangedFile {
|
|
172
|
+
path: string;
|
|
173
|
+
previous_path: string | null;
|
|
174
|
+
status: "added" | "modified" | "deleted" | "renamed" | "binary";
|
|
175
|
+
additions: number;
|
|
176
|
+
deletions: number;
|
|
177
|
+
surfaces: string[];
|
|
178
|
+
protected: boolean;
|
|
179
|
+
}
|
|
180
|
+
export interface LaneResult {
|
|
181
|
+
lane: Lane;
|
|
182
|
+
reviewer: string;
|
|
183
|
+
status: ValidationStatus;
|
|
184
|
+
findings: number;
|
|
185
|
+
notes: string[];
|
|
186
|
+
}
|
|
187
|
+
export interface ApprovalEvaluation {
|
|
188
|
+
approval_id: string;
|
|
189
|
+
approver_id: string;
|
|
190
|
+
status: "VALID" | "STALE" | "INVALID_SIGNATURE" | "UNSIGNED" | "UNKNOWN_APPROVER" | "NOT_HUMAN" | "SELF_APPROVAL" | "WRONG_SCOPE" | "REJECTED";
|
|
191
|
+
reason: string;
|
|
192
|
+
}
|
|
193
|
+
export interface ChangeReview {
|
|
194
|
+
schema_version: typeof ASSURANCE_SCHEMA_VERSION;
|
|
195
|
+
kind: "change-review";
|
|
196
|
+
methodology_version: typeof METHODOLOGY_VERSION;
|
|
197
|
+
review_id: string;
|
|
198
|
+
mode: "PR_DIFF" | "PRE_PR_LOCAL";
|
|
199
|
+
/** What approvals of this change are scoped to (approve --scope change:<ref>). */
|
|
200
|
+
change_ref: string;
|
|
201
|
+
created_at: string;
|
|
202
|
+
target: {
|
|
203
|
+
name: string;
|
|
204
|
+
repository: string;
|
|
205
|
+
};
|
|
206
|
+
base_sha: string | null;
|
|
207
|
+
head_sha: string | null;
|
|
208
|
+
author: {
|
|
209
|
+
id: string | null;
|
|
210
|
+
source: "flag" | "git" | "unknown";
|
|
211
|
+
trusted: boolean;
|
|
212
|
+
};
|
|
213
|
+
diff: {
|
|
214
|
+
source: "git" | "file";
|
|
215
|
+
digest: string;
|
|
216
|
+
files: ChangedFile[];
|
|
217
|
+
truncated: boolean;
|
|
218
|
+
};
|
|
219
|
+
risk: {
|
|
220
|
+
level: RiskLevel;
|
|
221
|
+
effective_level: "LOW" | "MEDIUM" | "HIGH";
|
|
222
|
+
surfaces: string[];
|
|
223
|
+
reasons: string[];
|
|
224
|
+
protected_paths: string[];
|
|
225
|
+
impact_check: {
|
|
226
|
+
schema_version: string;
|
|
227
|
+
high: number;
|
|
228
|
+
affected_modules: number;
|
|
229
|
+
commit: string | null;
|
|
230
|
+
} | null;
|
|
231
|
+
};
|
|
232
|
+
approvals_required: {
|
|
233
|
+
humans: number;
|
|
234
|
+
domain_owners: string[];
|
|
235
|
+
waiver: {
|
|
236
|
+
eligible: boolean;
|
|
237
|
+
applied: boolean;
|
|
238
|
+
reason: string;
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
approvals: ApprovalEvaluation[];
|
|
242
|
+
lanes: LaneResult[];
|
|
243
|
+
findings: ReviewFinding[];
|
|
244
|
+
disproven: ReviewFinding[];
|
|
245
|
+
checks: {
|
|
246
|
+
proof_run_id: string | null;
|
|
247
|
+
status: ValidationStatus;
|
|
248
|
+
detail: string;
|
|
249
|
+
};
|
|
250
|
+
baseline: {
|
|
251
|
+
run_id: string;
|
|
252
|
+
verdict: string;
|
|
253
|
+
results_sha256: string;
|
|
254
|
+
} | null;
|
|
255
|
+
cycle: {
|
|
256
|
+
number: number;
|
|
257
|
+
max: number;
|
|
258
|
+
previous_review_id: string | null;
|
|
259
|
+
repeated_unchanged: string[];
|
|
260
|
+
new_findings: string[];
|
|
261
|
+
escalated: boolean;
|
|
262
|
+
};
|
|
263
|
+
gate: {
|
|
264
|
+
status: GateStatus;
|
|
265
|
+
reasons: string[];
|
|
266
|
+
blocking_finding_ids: string[];
|
|
267
|
+
};
|
|
268
|
+
product_vs_change: string;
|
|
269
|
+
limitations: string[];
|
|
270
|
+
evidence_ids: string[];
|
|
271
|
+
provenance: {
|
|
272
|
+
tool: "product-check";
|
|
273
|
+
tool_version: string;
|
|
274
|
+
policy_id: string | null;
|
|
275
|
+
policy_digest: string | null;
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
export interface Approval {
|
|
279
|
+
schema_version: typeof ASSURANCE_SCHEMA_VERSION;
|
|
280
|
+
kind: "approval";
|
|
281
|
+
approval_id: string;
|
|
282
|
+
approver: {
|
|
283
|
+
id: string;
|
|
284
|
+
type: "human" | "bot";
|
|
285
|
+
};
|
|
286
|
+
scope: {
|
|
287
|
+
type: "change" | "closure" | "risk-acceptance" | "false-positive" | "release" | "continue";
|
|
288
|
+
ref: string;
|
|
289
|
+
};
|
|
290
|
+
reviewed_sha: string;
|
|
291
|
+
decision: "approve" | "reject";
|
|
292
|
+
comment: string;
|
|
293
|
+
created_at: string;
|
|
294
|
+
signature: {
|
|
295
|
+
algorithm: "ed25519";
|
|
296
|
+
key_id: string;
|
|
297
|
+
value: string;
|
|
298
|
+
} | null;
|
|
299
|
+
}
|
|
300
|
+
export interface VerificationCheckResult {
|
|
301
|
+
check_id: string;
|
|
302
|
+
kind: "control-reassessment" | "rule-recheck" | "command" | "no-new-critical" | "no-regressed-controls" | "gate-recheck";
|
|
303
|
+
role: "targeted" | "regression";
|
|
304
|
+
status: ValidationStatus;
|
|
305
|
+
detail: string;
|
|
306
|
+
execution_id: string | null;
|
|
307
|
+
evidence_ids: string[];
|
|
308
|
+
}
|
|
309
|
+
export interface VerificationRun {
|
|
310
|
+
schema_version: typeof ASSURANCE_SCHEMA_VERSION;
|
|
311
|
+
kind: "verification-run";
|
|
312
|
+
verification_id: string;
|
|
313
|
+
purpose: "closure" | "author-proof";
|
|
314
|
+
finding_ids: string[];
|
|
315
|
+
fix_id: string | null;
|
|
316
|
+
exact_sha: string | null;
|
|
317
|
+
worktree_clean: boolean | null;
|
|
318
|
+
environment: EnvironmentRecord;
|
|
319
|
+
started_at: string;
|
|
320
|
+
finished_at: string;
|
|
321
|
+
checks: VerificationCheckResult[];
|
|
322
|
+
executions: CommandExecution[];
|
|
323
|
+
before_evidence_ids: string[];
|
|
324
|
+
after_evidence_ids: string[];
|
|
325
|
+
after_results: {
|
|
326
|
+
run_id: string;
|
|
327
|
+
results_path: string;
|
|
328
|
+
results_sha256: string;
|
|
329
|
+
verdict: string;
|
|
330
|
+
open: Record<Severity, number>;
|
|
331
|
+
} | null;
|
|
332
|
+
result: "PASS" | "FAIL" | "INCONCLUSIVE";
|
|
333
|
+
reasons: string[];
|
|
334
|
+
limitations: string[];
|
|
335
|
+
actor: Actor;
|
|
336
|
+
}
|
|
337
|
+
export interface FixPacket {
|
|
338
|
+
schema_version: typeof ASSURANCE_SCHEMA_VERSION;
|
|
339
|
+
kind: "fix-packet";
|
|
340
|
+
methodology_version: typeof METHODOLOGY_VERSION;
|
|
341
|
+
fix_id: string;
|
|
342
|
+
finding_ids: string[];
|
|
343
|
+
source_finding_ids: string[];
|
|
344
|
+
title: string;
|
|
345
|
+
status: FixStatus;
|
|
346
|
+
risk_class: RiskLevel;
|
|
347
|
+
severity: Severity;
|
|
348
|
+
affected_surfaces: {
|
|
349
|
+
path: string | null;
|
|
350
|
+
line: number | null;
|
|
351
|
+
runtime: string | null;
|
|
352
|
+
}[];
|
|
353
|
+
user_impact: string;
|
|
354
|
+
evidence_refs: string[];
|
|
355
|
+
root_cause: {
|
|
356
|
+
hypothesis: string;
|
|
357
|
+
certainty: Epistemic;
|
|
358
|
+
confidence: Confidence;
|
|
359
|
+
};
|
|
360
|
+
constraints: string[];
|
|
361
|
+
proposed_change: string;
|
|
362
|
+
tests_required: {
|
|
363
|
+
id: string;
|
|
364
|
+
kind: "reproduction" | "regression" | "negative";
|
|
365
|
+
description: string;
|
|
366
|
+
/** Commands are never invented: null until the implementer declares one with `product-check fix`. */
|
|
367
|
+
argv: string[] | null;
|
|
368
|
+
}[];
|
|
369
|
+
negative_cases: string[];
|
|
370
|
+
dependencies: string[];
|
|
371
|
+
acceptance_conditions: string[];
|
|
372
|
+
owner: {
|
|
373
|
+
id: string | null;
|
|
374
|
+
accountable_human_required: true;
|
|
375
|
+
};
|
|
376
|
+
rollback_plan: string;
|
|
377
|
+
scope_guard: {
|
|
378
|
+
allowed_paths: string[];
|
|
379
|
+
note: string;
|
|
380
|
+
};
|
|
381
|
+
traceability: {
|
|
382
|
+
control_or_rule: string;
|
|
383
|
+
spec_requirement_ids: string[];
|
|
384
|
+
locations: string[];
|
|
385
|
+
};
|
|
386
|
+
implementation: {
|
|
387
|
+
change_sha: string | null;
|
|
388
|
+
implemented_by: Actor | null;
|
|
389
|
+
};
|
|
390
|
+
generated_from: {
|
|
391
|
+
results_run_id: string | null;
|
|
392
|
+
review_id: string | null;
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
export interface Transition {
|
|
396
|
+
seq: number;
|
|
397
|
+
from: ClosureState | null;
|
|
398
|
+
to: ClosureState;
|
|
399
|
+
at: string;
|
|
400
|
+
actor: Actor;
|
|
401
|
+
reason: string;
|
|
402
|
+
refs: {
|
|
403
|
+
evidence_ids: string[];
|
|
404
|
+
verification_id: string | null;
|
|
405
|
+
approval_ids: string[];
|
|
406
|
+
fix_id: string | null;
|
|
407
|
+
run_id: string | null;
|
|
408
|
+
};
|
|
409
|
+
prev_digest: string | null;
|
|
410
|
+
digest: string;
|
|
411
|
+
}
|
|
412
|
+
export interface RegisterEntry {
|
|
413
|
+
finding_id: string;
|
|
414
|
+
source: "baseline-scan" | "change-review" | "incident";
|
|
415
|
+
source_finding_id: string;
|
|
416
|
+
rule_or_control: string;
|
|
417
|
+
title: string;
|
|
418
|
+
severity: Severity;
|
|
419
|
+
pillar: Pillar;
|
|
420
|
+
epistemic: Epistemic;
|
|
421
|
+
location: string;
|
|
422
|
+
dedupe_key: string;
|
|
423
|
+
verification: FindingVerification;
|
|
424
|
+
state: ClosureState;
|
|
425
|
+
opened_in: string;
|
|
426
|
+
cycles: number;
|
|
427
|
+
escalated: boolean;
|
|
428
|
+
fix_ids: string[];
|
|
429
|
+
verification_ids: string[];
|
|
430
|
+
approval_ids: string[];
|
|
431
|
+
acceptance_id: string | null;
|
|
432
|
+
regressions: {
|
|
433
|
+
at: string;
|
|
434
|
+
run_id: string;
|
|
435
|
+
previous_state: ClosureState;
|
|
436
|
+
}[];
|
|
437
|
+
closure: {
|
|
438
|
+
closure_id: string;
|
|
439
|
+
actor: Actor;
|
|
440
|
+
at: string;
|
|
441
|
+
verification_id: string;
|
|
442
|
+
approval_ids: string[];
|
|
443
|
+
} | null;
|
|
444
|
+
transitions: Transition[];
|
|
445
|
+
}
|
|
446
|
+
export interface FixRecord {
|
|
447
|
+
fix_id: string;
|
|
448
|
+
finding_ids: string[];
|
|
449
|
+
change_sha: string;
|
|
450
|
+
implemented_by: Actor;
|
|
451
|
+
status: FixStatus;
|
|
452
|
+
reproduction: CommandSpec[];
|
|
453
|
+
recorded_at: string;
|
|
454
|
+
}
|
|
455
|
+
export interface RiskAcceptanceV32 {
|
|
456
|
+
acceptance_id: string;
|
|
457
|
+
finding_id: string;
|
|
458
|
+
owner: string;
|
|
459
|
+
scope: string;
|
|
460
|
+
rationale: string;
|
|
461
|
+
compensating_controls: string[];
|
|
462
|
+
review_date: string;
|
|
463
|
+
expires: string;
|
|
464
|
+
closure_condition: string;
|
|
465
|
+
approval_ids: string[];
|
|
466
|
+
created_at: string;
|
|
467
|
+
}
|
|
468
|
+
export interface CorpusCase {
|
|
469
|
+
case_id: string;
|
|
470
|
+
finding_id: string;
|
|
471
|
+
origin: "escaped-incident" | "regressed-finding" | "verified-fix";
|
|
472
|
+
description: string;
|
|
473
|
+
command: CommandSpec;
|
|
474
|
+
added_at: string;
|
|
475
|
+
added_by: Actor;
|
|
476
|
+
}
|
|
477
|
+
export interface ClosureRegister {
|
|
478
|
+
schema_version: typeof ASSURANCE_SCHEMA_VERSION;
|
|
479
|
+
kind: "closure-register";
|
|
480
|
+
methodology_version: typeof METHODOLOGY_VERSION;
|
|
481
|
+
register_id: string;
|
|
482
|
+
target: {
|
|
483
|
+
name: string;
|
|
484
|
+
repository: string | null;
|
|
485
|
+
};
|
|
486
|
+
created_at: string;
|
|
487
|
+
updated_at: string;
|
|
488
|
+
baseline: {
|
|
489
|
+
run_id: string;
|
|
490
|
+
results_sha256: string;
|
|
491
|
+
baseline_sha: string | null;
|
|
492
|
+
};
|
|
493
|
+
settings: {
|
|
494
|
+
max_cycles: number;
|
|
495
|
+
};
|
|
496
|
+
entries: RegisterEntry[];
|
|
497
|
+
fixes: FixRecord[];
|
|
498
|
+
verification_runs: VerificationRun[];
|
|
499
|
+
approvals: Approval[];
|
|
500
|
+
risk_acceptances: RiskAcceptanceV32[];
|
|
501
|
+
regression_corpus: CorpusCase[];
|
|
502
|
+
chain: {
|
|
503
|
+
length: number;
|
|
504
|
+
head_digest: string | null;
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
export interface PhaseProgress {
|
|
508
|
+
phase: VcPhase;
|
|
509
|
+
name: string;
|
|
510
|
+
status: "COMPLETED" | "PARTIAL" | "NOT_STARTED" | "NOT_SUPPORTED";
|
|
511
|
+
evidence: string[];
|
|
512
|
+
}
|
|
513
|
+
export interface GateCondition {
|
|
514
|
+
id: string;
|
|
515
|
+
title: string;
|
|
516
|
+
status: "PASS" | "FAIL" | "NOT_EVALUATED";
|
|
517
|
+
detail: string;
|
|
518
|
+
refs: string[];
|
|
519
|
+
}
|
|
520
|
+
export interface ReleaseGate {
|
|
521
|
+
schema_version: typeof ASSURANCE_SCHEMA_VERSION;
|
|
522
|
+
kind: "release-gate";
|
|
523
|
+
methodology_version: typeof METHODOLOGY_VERSION;
|
|
524
|
+
gate_id: string;
|
|
525
|
+
evaluated_at: string;
|
|
526
|
+
target: {
|
|
527
|
+
name: string;
|
|
528
|
+
repository: string | null;
|
|
529
|
+
};
|
|
530
|
+
head_sha: string | null;
|
|
531
|
+
decision: ReleaseDecision;
|
|
532
|
+
product_verdict: string;
|
|
533
|
+
change_gate: GateStatus | null;
|
|
534
|
+
conditions: GateCondition[];
|
|
535
|
+
blocking_reasons: string[];
|
|
536
|
+
open: Record<Severity, number>;
|
|
537
|
+
open_blocking_ids: string[];
|
|
538
|
+
accepted_risks: {
|
|
539
|
+
acceptance_id: string;
|
|
540
|
+
finding_id: string;
|
|
541
|
+
expires: string;
|
|
542
|
+
status: "ACTIVE" | "REVIEW_DUE" | "EXPIRED";
|
|
543
|
+
}[];
|
|
544
|
+
approvals: {
|
|
545
|
+
required: number;
|
|
546
|
+
valid: number;
|
|
547
|
+
stale: number;
|
|
548
|
+
};
|
|
549
|
+
executed_checks: {
|
|
550
|
+
count: number;
|
|
551
|
+
passed: number;
|
|
552
|
+
failed: number;
|
|
553
|
+
not_run: number;
|
|
554
|
+
};
|
|
555
|
+
coverage: {
|
|
556
|
+
customer: number;
|
|
557
|
+
technology: number;
|
|
558
|
+
trust: number;
|
|
559
|
+
overall: number;
|
|
560
|
+
gaps: string[];
|
|
561
|
+
};
|
|
562
|
+
rollback: {
|
|
563
|
+
declared: boolean;
|
|
564
|
+
ref: string | null;
|
|
565
|
+
};
|
|
566
|
+
phases: PhaseProgress[];
|
|
567
|
+
inputs: {
|
|
568
|
+
results_sha256: string;
|
|
569
|
+
register_digest: string | null;
|
|
570
|
+
review_id: string | null;
|
|
571
|
+
proof_run_id: string | null;
|
|
572
|
+
};
|
|
573
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const ASSURANCE_SCHEMA_VERSION = "0.1.0";
|
|
2
|
+
/**
|
|
3
|
+
* Methodology version the assurance design was inspired by (CLEAR CT v3.2, NetworkGain Consulting; internal and
|
|
4
|
+
* unpublished). Relationship `inspired` per the Commons attribution standard: no text or catalog is reproduced.
|
|
5
|
+
*/
|
|
6
|
+
export const METHODOLOGY_VERSION = "CT3 v3.2";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** JSON with object keys sorted recursively, so equal content always has the same digest. */
|
|
2
|
+
export declare function canonicalJson(value: unknown): string;
|
|
3
|
+
/** SHA-256 of the canonical JSON of a value. */
|
|
4
|
+
export declare const digestOf: (value: unknown) => string;
|
|
5
|
+
/** Converts a glob (`**`, `*`, `?`) to an anchored regular expression over forward-slash paths. */
|
|
6
|
+
export declare function globToRegExp(glob: string): RegExp;
|
|
7
|
+
/** True when the path matches any of the globs. */
|
|
8
|
+
export declare const matchesAny: (path: string, globs: readonly string[]) => boolean;
|
|
9
|
+
/** Reads and parses a JSON file; failures are input errors (exit 3). */
|
|
10
|
+
export declare function readJson<T = unknown>(path: string, label?: string): T;
|
|
11
|
+
/** Writes text atomically (temp file + rename), creating parent directories. */
|
|
12
|
+
export declare function writeText(path: string, content: string): void;
|
|
13
|
+
/** Writes pretty JSON atomically. */
|
|
14
|
+
export declare const writeJson: (path: string, value: unknown) => void;
|
|
15
|
+
/** Reads a JSON file when it exists. */
|
|
16
|
+
export declare const readJsonIfExists: <T>(path: string) => T | null;
|
|
17
|
+
/** YYYY-MM-DD validation. */
|
|
18
|
+
export declare const isIsoDate: (s: string) => boolean;
|
|
19
|
+
/** Days from `now` until the end of a YYYY-MM-DD date (negative when past). */
|
|
20
|
+
export declare function daysUntil(date: string, now: Date): number;
|
|
21
|
+
/** Zero-padded sequential identifier: F-001, FX-002, VR-010. */
|
|
22
|
+
export declare const seqId: (prefix: string, n: number) => string;
|
|
23
|
+
/** A git object name or ref that is safe to pass as an argument (never an option). */
|
|
24
|
+
export declare function assertRef(ref: string, label: string): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 EnWithAI Technologies
|
|
3
|
+
/**
|
|
4
|
+
* Small helpers for the assurance layer: canonical JSON (for digests and signatures), glob matching, JSON file I/O
|
|
5
|
+
* with atomic writes, and ISO date handling.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { inputError } from "../errors.js";
|
|
10
|
+
import { sha256 } from "../util/hash.js";
|
|
11
|
+
/** JSON with object keys sorted recursively, so equal content always has the same digest. */
|
|
12
|
+
export function canonicalJson(value) {
|
|
13
|
+
if (value === null || typeof value !== "object")
|
|
14
|
+
return JSON.stringify(value) ?? "null";
|
|
15
|
+
if (Array.isArray(value))
|
|
16
|
+
return `[${value.map((v) => canonicalJson(v)).join(",")}]`;
|
|
17
|
+
const obj = value;
|
|
18
|
+
return `{${Object.keys(obj)
|
|
19
|
+
.filter((k) => obj[k] !== undefined)
|
|
20
|
+
.sort()
|
|
21
|
+
.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`)
|
|
22
|
+
.join(",")}}`;
|
|
23
|
+
}
|
|
24
|
+
/** SHA-256 of the canonical JSON of a value. */
|
|
25
|
+
export const digestOf = (value) => sha256(canonicalJson(value));
|
|
26
|
+
/** Converts a glob (`**`, `*`, `?`) to an anchored regular expression over forward-slash paths. */
|
|
27
|
+
export function globToRegExp(glob) {
|
|
28
|
+
let re = "";
|
|
29
|
+
for (let i = 0; i < glob.length; i++) {
|
|
30
|
+
const ch = glob[i];
|
|
31
|
+
if (ch === "*") {
|
|
32
|
+
if (glob[i + 1] === "*") {
|
|
33
|
+
const slash = glob[i + 2] === "/";
|
|
34
|
+
re += slash ? "(?:.*/)?" : ".*";
|
|
35
|
+
i += slash ? 2 : 1;
|
|
36
|
+
}
|
|
37
|
+
else
|
|
38
|
+
re += "[^/]*";
|
|
39
|
+
}
|
|
40
|
+
else if (ch === "?")
|
|
41
|
+
re += "[^/]";
|
|
42
|
+
else
|
|
43
|
+
re += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
44
|
+
}
|
|
45
|
+
return new RegExp(`^${re}$`);
|
|
46
|
+
}
|
|
47
|
+
/** True when the path matches any of the globs. */
|
|
48
|
+
export const matchesAny = (path, globs) => globs.some((g) => globToRegExp(g).test(path));
|
|
49
|
+
/** Reads and parses a JSON file; failures are input errors (exit 3). */
|
|
50
|
+
export function readJson(path, label = path) {
|
|
51
|
+
let text;
|
|
52
|
+
try {
|
|
53
|
+
text = readFileSync(path, "utf8");
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw inputError(`Cannot read ${label}`);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(text);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw inputError(`Not valid JSON: ${label}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Writes text atomically (temp file + rename), creating parent directories. */
|
|
66
|
+
export function writeText(path, content) {
|
|
67
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
68
|
+
const temp = join(dirname(path), `.pc-${process.pid}-${Math.random().toString(36).slice(2)}.tmp`);
|
|
69
|
+
writeFileSync(temp, content, { mode: 0o644 });
|
|
70
|
+
renameSync(temp, path);
|
|
71
|
+
}
|
|
72
|
+
/** Writes pretty JSON atomically. */
|
|
73
|
+
export const writeJson = (path, value) => writeText(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
74
|
+
/** Reads a JSON file when it exists. */
|
|
75
|
+
export const readJsonIfExists = (path) => (existsSync(path) ? readJson(path) : null);
|
|
76
|
+
/** YYYY-MM-DD validation. */
|
|
77
|
+
export const isIsoDate = (s) => /^\d{4}-\d{2}-\d{2}$/.test(s) && !Number.isNaN(Date.parse(s));
|
|
78
|
+
/** Days from `now` until the end of a YYYY-MM-DD date (negative when past). */
|
|
79
|
+
export function daysUntil(date, now) {
|
|
80
|
+
return Math.floor((Date.parse(`${date}T23:59:59.999Z`) - now.getTime()) / 86_400_000);
|
|
81
|
+
}
|
|
82
|
+
/** Zero-padded sequential identifier: F-001, FX-002, VR-010. */
|
|
83
|
+
export const seqId = (prefix, n) => `${prefix}-${String(n).padStart(3, "0")}`;
|
|
84
|
+
/** A git object name or ref that is safe to pass as an argument (never an option). */
|
|
85
|
+
export function assertRef(ref, label) {
|
|
86
|
+
if (!/^[A-Za-z0-9._/@^~{}][A-Za-z0-9._/@^~{}:-]*$/.test(ref) || ref.includes(".."))
|
|
87
|
+
throw inputError(`${label} is not a valid git revision: ${JSON.stringify(ref)}`);
|
|
88
|
+
return ref;
|
|
89
|
+
}
|