@mstar-harness/engine 3.10.2 → 3.11.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/audit.d.ts +52 -1
- package/dist/audit.js +269 -13
- package/dist/coordination-write.d.ts +5 -3
- package/dist/coordination.d.ts +5 -0
- package/dist/engine.js +764 -37
- package/dist/index.d.ts +4 -4
- package/dist/workflow.d.ts +80 -1
- package/package.json +1 -1
package/dist/audit.d.ts
CHANGED
|
@@ -167,11 +167,47 @@ export type AuditFinding = {
|
|
|
167
167
|
effort: AuditEffort;
|
|
168
168
|
risk: AuditRisk;
|
|
169
169
|
confidence: AuditConfidence;
|
|
170
|
-
|
|
170
|
+
/** Free-text string (legacy form, rendered byte-for-byte) or a structured
|
|
171
|
+
* location. Authored JSON should prefer the structured form. */
|
|
172
|
+
evidence: readonly (string | AuditEvidence)[];
|
|
171
173
|
priority: AuditPriority;
|
|
172
174
|
fixSketch?: string;
|
|
173
175
|
verification?: string;
|
|
174
176
|
dependsOn?: string;
|
|
177
|
+
/** Source-derived root-cause identity (audit-finding-contract.md §4).
|
|
178
|
+
* Optional; never invented by the engine. */
|
|
179
|
+
fingerprint?: string;
|
|
180
|
+
/** Entry-point → propagation → sink path (§5). Supplements evidence. */
|
|
181
|
+
trace?: readonly AuditTraceStep[];
|
|
182
|
+
/** Author-supplied severity assessment (§6). Never inferred from
|
|
183
|
+
* priority/risk/confidence. */
|
|
184
|
+
severity?: AuditSeverity;
|
|
185
|
+
};
|
|
186
|
+
/** One structured evidence location. `file` is a safe repository-relative
|
|
187
|
+
* POSIX path; `line` is omitted when unknown (never invented). */
|
|
188
|
+
export type AuditEvidence = {
|
|
189
|
+
file: string;
|
|
190
|
+
line?: number;
|
|
191
|
+
description: string;
|
|
192
|
+
};
|
|
193
|
+
/** Trace-step kind (§5): where data enters, how it travels, where it lands. */
|
|
194
|
+
export type AuditTraceKind = "entrypoint" | "propagation" | "sink";
|
|
195
|
+
/** One trace step. `line` is a positive safe integer; `scope` names the
|
|
196
|
+
* enclosing route/function/module. */
|
|
197
|
+
export type AuditTraceStep = {
|
|
198
|
+
kind: AuditTraceKind;
|
|
199
|
+
file: string;
|
|
200
|
+
line: number;
|
|
201
|
+
scope: string;
|
|
202
|
+
description: string;
|
|
203
|
+
};
|
|
204
|
+
/** Severity rank ordinal labels (§6). Harness diagnostic `Severity` is a
|
|
205
|
+
* DIFFERENT enum (`nit`, no `informational`) — never conflate them. */
|
|
206
|
+
export type AuditSeverityRank = "informational" | "low" | "medium" | "high" | "critical";
|
|
207
|
+
export type AuditSeverity = {
|
|
208
|
+
likelihood: AuditSeverityRank;
|
|
209
|
+
impact: AuditSeverityRank;
|
|
210
|
+
overall: AuditSeverityRank;
|
|
175
211
|
};
|
|
176
212
|
/** Options for `scaffoldAuditPlan`. `plannedAt` defaults to the
|
|
177
213
|
* `repoShortSha` + `date`; `date` defaults to today (YYYY-MM-DD). */
|
|
@@ -205,6 +241,21 @@ export type ScaffoldAuditPlanResult = {
|
|
|
205
241
|
files: string[];
|
|
206
242
|
nextNumber: number;
|
|
207
243
|
};
|
|
244
|
+
/**
|
|
245
|
+
* Deterministic finding gates (audit-finding-contract.md §§2–6): fingerprint
|
|
246
|
+
* grammar / exact uniqueness / ordinal ordering of the supplied subsequence,
|
|
247
|
+
* `severity.overall ≤ severity.impact`, nonempty trace topology, positive
|
|
248
|
+
* safe-integer trace lines, safe typed paths, visible well-formed text, and
|
|
249
|
+
* opaque-field credential rejection (a fingerprint or typed location that
|
|
250
|
+
* `redactSecrets` would alter is REJECTED, never redacted into a different
|
|
251
|
+
* identity). Contextual judgement (reportability, semantic exclusion,
|
|
252
|
+
* coverage) stays in skill prose (§2).
|
|
253
|
+
*
|
|
254
|
+
* Violation codes are stable `audit.finding.<family>.<rule>`; messages carry
|
|
255
|
+
* `findings[index].field` paths only — never raw submitted values. Pure:
|
|
256
|
+
* the input is never mutated and nothing is sorted or repaired.
|
|
257
|
+
*/
|
|
258
|
+
export declare function validateAuditFindingGates(findings: readonly AuditFinding[]): GateResult;
|
|
208
259
|
/**
|
|
209
260
|
* Scaffold an audit plan directory (`{PLAN_DIR}/audit-<date>/` layout,
|
|
210
261
|
* mstar-audit SKILL.md § Plan output (all variants)): numbered `NNN-<slug>.md` plan files from
|
package/dist/audit.js
CHANGED
|
@@ -279,7 +279,7 @@ function validatePlanProgress(value, what = "coordination.progress") {
|
|
|
279
279
|
}
|
|
280
280
|
return violations;
|
|
281
281
|
}
|
|
282
|
-
function validatePlanHandoff(value, what = "coordination.handoff") {
|
|
282
|
+
function validatePlanHandoff(value, what = "coordination.handoff", route = "integration") {
|
|
283
283
|
if (!isPlainObject2(value))
|
|
284
284
|
return [invalid("coordination.row.handoff-shape", `${what} must be an object`)];
|
|
285
285
|
const allowed = [
|
|
@@ -419,7 +419,25 @@ function validatePlanHandoff(value, what = "coordination.handoff") {
|
|
|
419
419
|
}
|
|
420
420
|
}
|
|
421
421
|
if ((value.state === "integrating" || value.state === "merged" || value.state === "completed") && value.integration === undefined) {
|
|
422
|
-
|
|
422
|
+
if (route === "standalone-development" && value.state === "completed") {
|
|
423
|
+
if (value.completed_at === undefined) {
|
|
424
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.state completed requires completed_at for a standalone handoff`));
|
|
425
|
+
}
|
|
426
|
+
if (!isNonEmptyString(value.accepted_at)) {
|
|
427
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.accepted_at is required for a standalone completed handoff`));
|
|
428
|
+
}
|
|
429
|
+
if (!isNonEmptyString(value.accepted_by)) {
|
|
430
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.accepted_by is required for a standalone completed handoff`));
|
|
431
|
+
}
|
|
432
|
+
if (value.qc === undefined) {
|
|
433
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qc is required for a standalone completed handoff`));
|
|
434
|
+
}
|
|
435
|
+
if (value.qa === undefined) {
|
|
436
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qa is required for a standalone completed handoff`));
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.state ${String(value.state)} requires integration`));
|
|
440
|
+
}
|
|
423
441
|
}
|
|
424
442
|
return violations;
|
|
425
443
|
}
|
|
@@ -455,7 +473,7 @@ function validatePreparedCoordination(value, what = "coordination.prepared") {
|
|
|
455
473
|
}
|
|
456
474
|
return violations;
|
|
457
475
|
}
|
|
458
|
-
function validateRowCoordination(value, what = "coordination") {
|
|
476
|
+
function validateRowCoordination(value, what = "coordination", route = "integration") {
|
|
459
477
|
if (!isPlainObject2(value))
|
|
460
478
|
return [invalid("coordination.row.shape", `${what} must be an object`)];
|
|
461
479
|
const allowed = ["revision", "prepared", "session", "progress", "handoff"];
|
|
@@ -474,7 +492,7 @@ function validateRowCoordination(value, what = "coordination") {
|
|
|
474
492
|
if (value.progress !== undefined)
|
|
475
493
|
violations.push(...validatePlanProgress(value.progress, `${what}.progress`));
|
|
476
494
|
if (value.handoff !== undefined)
|
|
477
|
-
violations.push(...validatePlanHandoff(value.handoff, `${what}.handoff
|
|
495
|
+
violations.push(...validatePlanHandoff(value.handoff, `${what}.handoff`, route));
|
|
478
496
|
if (value.handoff !== undefined && value.session === undefined) {
|
|
479
497
|
violations.push(invalid("coordination.row.handoff-field", `${what}.handoff requires a bound plan session`));
|
|
480
498
|
}
|
|
@@ -797,6 +815,45 @@ var WORKFLOW_TERMINAL_STATUSES = ["completed", "failed", "stopped"];
|
|
|
797
815
|
var WORKFLOW_LIFECYCLE_TYPES = ["plan", "iteration"];
|
|
798
816
|
var WORKFLOW_DELIVERY_KINDS = ["development", "verification/report-only"];
|
|
799
817
|
var WORKFLOW_COMPOUND_OUTCOMES = ["created", "updated", "skipped"];
|
|
818
|
+
function isStandaloneDevelopmentWorkflow(snapshot) {
|
|
819
|
+
return snapshot.type === "plan" && snapshot.delivery_kind === "development" && Array.isArray(snapshot.plans) && snapshot.plans.length === 1;
|
|
820
|
+
}
|
|
821
|
+
function rowValidationRoute(snapshot, row) {
|
|
822
|
+
if (isStandaloneDevelopmentWorkflow(snapshot) && snapshot.plans[0]?.id === row.id) {
|
|
823
|
+
return "standalone-development";
|
|
824
|
+
}
|
|
825
|
+
return "integration";
|
|
826
|
+
}
|
|
827
|
+
function validateStandaloneCompletedCoherence(snapshot, row) {
|
|
828
|
+
const violations = [];
|
|
829
|
+
if (!isStandaloneDevelopmentWorkflow(snapshot) || row.id !== snapshot.plans[0]?.id)
|
|
830
|
+
return violations;
|
|
831
|
+
const coordination = row.coordination;
|
|
832
|
+
if (!isPlainObject2(coordination) || !isPlainObject2(coordination.handoff))
|
|
833
|
+
return violations;
|
|
834
|
+
const handoff = coordination.handoff;
|
|
835
|
+
if (handoff.state !== "completed" || handoff.integration !== undefined)
|
|
836
|
+
return violations;
|
|
837
|
+
if (row.status !== "Done") {
|
|
838
|
+
violations.push(violation2("high", "coordination.row.handoff-field", `standalone completed handoff requires row ${String(row.id)} to be Done`));
|
|
839
|
+
}
|
|
840
|
+
if (row.execution_lease !== undefined) {
|
|
841
|
+
violations.push(violation2("high", "coordination.row.handoff-field", `standalone completed handoff requires no execution lease on row ${String(row.id)}`));
|
|
842
|
+
}
|
|
843
|
+
if (snapshot.integration_merge_lease !== undefined) {
|
|
844
|
+
violations.push(violation2("high", "coordination.row.handoff-field", "standalone completed handoff requires no integration_merge_lease on the snapshot"));
|
|
845
|
+
}
|
|
846
|
+
const source = snapshot.branch?.source;
|
|
847
|
+
const target = snapshot.branch?.target;
|
|
848
|
+
if (!isNonEmptyString(source) || !isNonEmptyString(target)) {
|
|
849
|
+
violations.push(violation2("high", "coordination.row.handoff-field", "standalone completed handoff requires nonblank branch.source and branch.target"));
|
|
850
|
+
} else {
|
|
851
|
+
if (handoff.source_branch !== source) {
|
|
852
|
+
violations.push(violation2("high", "coordination.row.handoff-field", `standalone completed handoff source_branch ${String(handoff.source_branch)} must equal branch.source ${source}`));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return violations;
|
|
856
|
+
}
|
|
800
857
|
function stableJson(value) {
|
|
801
858
|
if (Array.isArray(value))
|
|
802
859
|
return `[${value.map(stableJson).join(",")}]`;
|
|
@@ -916,13 +973,17 @@ function validateWorkflowSnapshot(doc) {
|
|
|
916
973
|
} else if (!Array.isArray(doc.plans)) {
|
|
917
974
|
violations.push(violation2("high", "workflow.snapshot.invalid-plans", "plans must be an array of legacy plan rows"));
|
|
918
975
|
} else {
|
|
976
|
+
const snapshotDoc = doc;
|
|
919
977
|
for (const row of doc.plans) {
|
|
920
978
|
violations.push(...validatePlanRow(row).violations);
|
|
921
979
|
if (isPlainObject2(row) && row.execution_lease !== undefined) {
|
|
922
980
|
violations.push(...validateExecutionLease(row.execution_lease).violations);
|
|
923
981
|
}
|
|
924
982
|
if (isPlainObject2(row) && row.coordination !== undefined) {
|
|
925
|
-
|
|
983
|
+
const planRow = row;
|
|
984
|
+
const route = rowValidationRoute(snapshotDoc, planRow);
|
|
985
|
+
violations.push(...validateRowCoordination(row.coordination, `plans[${String(row.id)}].coordination`, route));
|
|
986
|
+
violations.push(...validateStandaloneCompletedCoherence(snapshotDoc, planRow));
|
|
926
987
|
}
|
|
927
988
|
}
|
|
928
989
|
}
|
|
@@ -1883,6 +1944,176 @@ function slugify(title) {
|
|
|
1883
1944
|
}
|
|
1884
1945
|
var escapeCell = (value) => value.replace(/\|/g, "\\|");
|
|
1885
1946
|
var truncate = (value, max) => value.length > max ? `${value.slice(0, max)}…` : value;
|
|
1947
|
+
var collapseEvidenceWs = (value) => value.replace(/\s*[\r\n]\s*/g, " ");
|
|
1948
|
+
var evidenceText = (item) => typeof item === "string" ? item : `${item.file}${item.line !== undefined ? `:${item.line}` : ""} — ${item.description}`;
|
|
1949
|
+
var hasEnrichedMetadata = (finding) => finding.fingerprint !== undefined || finding.trace !== undefined || finding.severity !== undefined || finding.evidence.some((item) => typeof item !== "string");
|
|
1950
|
+
var AUDIT_FINGERPRINT_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@+-]*(?![\s\S])/;
|
|
1951
|
+
var AUDIT_SEVERITY_ORDER = { informational: 0, low: 1, medium: 2, high: 3, critical: 4 };
|
|
1952
|
+
var DEFAULT_IGNORABLE_RE = /[\u00AD\u034F\u061C\u115F\u1160\u17B4\u17B5\u180B-\u180F\u200B-\u200F\u202A-\u202E\u2060-\u206F\u3164\uFE00-\uFE0F\uFEFF\uFFA0\uFFF0-\uFFF8\u{1BCA0}-\u{1BCA3}\u{1D173}-\u{1D17A}\u{E0000}-\u{E0FFF}]/u;
|
|
1953
|
+
var LONE_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
1954
|
+
var isPlainObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1955
|
+
function isVisibleText(value) {
|
|
1956
|
+
if (LONE_SURROGATE_RE.test(value))
|
|
1957
|
+
return false;
|
|
1958
|
+
for (const ch of value) {
|
|
1959
|
+
if (!/\p{White_Space}/u.test(ch) && !DEFAULT_IGNORABLE_RE.test(ch))
|
|
1960
|
+
return true;
|
|
1961
|
+
}
|
|
1962
|
+
return false;
|
|
1963
|
+
}
|
|
1964
|
+
function safeAuditPath(value) {
|
|
1965
|
+
if (typeof value !== "string")
|
|
1966
|
+
return false;
|
|
1967
|
+
if (value === "")
|
|
1968
|
+
return false;
|
|
1969
|
+
if (value.includes("\\"))
|
|
1970
|
+
return false;
|
|
1971
|
+
if (/[\u0000-\u001F\u007F-\u009F]/.test(value))
|
|
1972
|
+
return false;
|
|
1973
|
+
if (LONE_SURROGATE_RE.test(value))
|
|
1974
|
+
return false;
|
|
1975
|
+
if (value.startsWith("/"))
|
|
1976
|
+
return false;
|
|
1977
|
+
if (/^[A-Za-z]:/.test(value))
|
|
1978
|
+
return false;
|
|
1979
|
+
for (const segment of value.split("/")) {
|
|
1980
|
+
if (segment === "" || segment === "." || segment === "..")
|
|
1981
|
+
return false;
|
|
1982
|
+
if (segment.endsWith(".") || segment.endsWith(" "))
|
|
1983
|
+
return false;
|
|
1984
|
+
}
|
|
1985
|
+
return true;
|
|
1986
|
+
}
|
|
1987
|
+
function validateAuditFindingGates(findings) {
|
|
1988
|
+
const violations = [];
|
|
1989
|
+
const seen = new Map;
|
|
1990
|
+
let previousFingerprint;
|
|
1991
|
+
if (!Array.isArray(findings)) {
|
|
1992
|
+
violations.push(violation4("high", "audit.finding.shape", "findings — audit.finding.shape"));
|
|
1993
|
+
return { ok: false, violations };
|
|
1994
|
+
}
|
|
1995
|
+
findings.forEach((finding, index) => {
|
|
1996
|
+
const at = (field) => `findings[${index}].${field}`;
|
|
1997
|
+
const push = (code, field) => {
|
|
1998
|
+
violations.push(violation4("high", code, `${at(field)} — ${code}`));
|
|
1999
|
+
};
|
|
2000
|
+
if (!isPlainObject3(finding)) {
|
|
2001
|
+
violations.push(violation4("high", "audit.finding.shape", `findings[${index}] — audit.finding.shape`));
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
if (finding.fingerprint !== undefined) {
|
|
2005
|
+
const fingerprint = finding.fingerprint;
|
|
2006
|
+
if (typeof fingerprint !== "string" || !AUDIT_FINGERPRINT_RE.test(fingerprint))
|
|
2007
|
+
push("audit.finding.fingerprint.grammar", "fingerprint");
|
|
2008
|
+
else if (redactSecrets(fingerprint).text !== fingerprint)
|
|
2009
|
+
push("audit.finding.fingerprint.secret", "fingerprint");
|
|
2010
|
+
else {
|
|
2011
|
+
const firstAt = seen.get(fingerprint);
|
|
2012
|
+
if (firstAt !== undefined)
|
|
2013
|
+
push("audit.finding.fingerprint.duplicate", "fingerprint");
|
|
2014
|
+
else
|
|
2015
|
+
seen.set(fingerprint, index);
|
|
2016
|
+
if (previousFingerprint !== undefined && fingerprint < previousFingerprint) {
|
|
2017
|
+
push("audit.finding.fingerprint.order", "fingerprint");
|
|
2018
|
+
}
|
|
2019
|
+
previousFingerprint = fingerprint;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
if (finding.severity !== undefined) {
|
|
2023
|
+
if (!isPlainObject3(finding.severity)) {
|
|
2024
|
+
push("audit.finding.severity.shape", "severity");
|
|
2025
|
+
} else {
|
|
2026
|
+
const { likelihood, impact, overall } = finding.severity;
|
|
2027
|
+
const rankOf = (r) => AUDIT_SEVERITY_ORDER[r];
|
|
2028
|
+
for (const [field, value] of [
|
|
2029
|
+
["likelihood", likelihood],
|
|
2030
|
+
["impact", impact],
|
|
2031
|
+
["overall", overall]
|
|
2032
|
+
]) {
|
|
2033
|
+
if (rankOf(value) === undefined)
|
|
2034
|
+
push("audit.finding.severity.rank", `severity.${field}`);
|
|
2035
|
+
}
|
|
2036
|
+
const impactRank = rankOf(impact);
|
|
2037
|
+
const overallRank = rankOf(overall);
|
|
2038
|
+
if (impactRank !== undefined && overallRank !== undefined && overallRank > impactRank) {
|
|
2039
|
+
push("audit.finding.severity.overall-exceeds-impact", "severity.overall");
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
const textViolation = (field, value) => {
|
|
2044
|
+
if (value === undefined)
|
|
2045
|
+
return;
|
|
2046
|
+
if (typeof value !== "string")
|
|
2047
|
+
push("audit.finding.text.type", field);
|
|
2048
|
+
else if (LONE_SURROGATE_RE.test(value))
|
|
2049
|
+
push("audit.finding.text.surrogate", field);
|
|
2050
|
+
else if (!isVisibleText(value))
|
|
2051
|
+
push("audit.finding.text.invisible", field);
|
|
2052
|
+
};
|
|
2053
|
+
if (finding.trace !== undefined) {
|
|
2054
|
+
if (!Array.isArray(finding.trace))
|
|
2055
|
+
push("audit.finding.trace.shape", "trace");
|
|
2056
|
+
else if (finding.trace.length === 0)
|
|
2057
|
+
push("audit.finding.trace.empty", "trace");
|
|
2058
|
+
else {
|
|
2059
|
+
const kinds = finding.trace.map((s) => isPlainObject3(s) ? s.kind : undefined);
|
|
2060
|
+
const topologyOk = finding.trace.length === 1 ? kinds[0] === "entrypoint" || kinds[0] === "sink" : kinds[0] === "entrypoint" && kinds[kinds.length - 1] === "sink" && kinds.slice(1, -1).every((k) => k === "propagation");
|
|
2061
|
+
if (!topologyOk)
|
|
2062
|
+
push("audit.finding.trace.topology", "trace");
|
|
2063
|
+
finding.trace.forEach((step, stepIndex) => {
|
|
2064
|
+
const stepAt = `trace[${stepIndex}]`;
|
|
2065
|
+
if (!isPlainObject3(step)) {
|
|
2066
|
+
push("audit.finding.trace.shape", stepAt);
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
if (typeof step.line !== "number" || !Number.isSafeInteger(step.line) || step.line <= 0) {
|
|
2070
|
+
push("audit.finding.trace.line", `${stepAt}.line`);
|
|
2071
|
+
}
|
|
2072
|
+
const stepFile = step.file;
|
|
2073
|
+
if (typeof stepFile !== "string" || !safeAuditPath(stepFile))
|
|
2074
|
+
push("audit.finding.path.unsafe", `${stepAt}.file`);
|
|
2075
|
+
else if (redactSecrets(stepFile).text !== stepFile)
|
|
2076
|
+
push("audit.finding.path.secret", `${stepAt}.file`);
|
|
2077
|
+
if (step.scope === undefined)
|
|
2078
|
+
push("audit.finding.trace.shape", `${stepAt}.scope`);
|
|
2079
|
+
else
|
|
2080
|
+
textViolation(`${stepAt}.scope`, step.scope);
|
|
2081
|
+
if (step.description === undefined)
|
|
2082
|
+
push("audit.finding.trace.shape", `${stepAt}.description`);
|
|
2083
|
+
else
|
|
2084
|
+
textViolation(`${stepAt}.description`, step.description);
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
if (!Array.isArray(finding.evidence))
|
|
2089
|
+
push("audit.finding.evidence.shape", "evidence");
|
|
2090
|
+
else
|
|
2091
|
+
finding.evidence.forEach((item, itemIndex) => {
|
|
2092
|
+
if (typeof item === "string") {
|
|
2093
|
+
textViolation(`evidence[${itemIndex}]`, item);
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
if (!isPlainObject3(item)) {
|
|
2097
|
+
push("audit.finding.evidence.shape", `evidence[${itemIndex}]`);
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
const itemFile = item.file;
|
|
2101
|
+
if (typeof itemFile !== "string" || !safeAuditPath(itemFile))
|
|
2102
|
+
push("audit.finding.path.unsafe", `evidence[${itemIndex}].file`);
|
|
2103
|
+
else if (redactSecrets(itemFile).text !== itemFile)
|
|
2104
|
+
push("audit.finding.path.secret", `evidence[${itemIndex}].file`);
|
|
2105
|
+
if (item.line !== undefined && (typeof item.line !== "number" || !Number.isSafeInteger(item.line) || item.line <= 0)) {
|
|
2106
|
+
push("audit.finding.evidence.line", `evidence[${itemIndex}].line`);
|
|
2107
|
+
}
|
|
2108
|
+
textViolation(`evidence[${itemIndex}].description`, item.description);
|
|
2109
|
+
});
|
|
2110
|
+
textViolation("title", finding.title);
|
|
2111
|
+
textViolation("impact", finding.impact);
|
|
2112
|
+
textViolation("fixSketch", finding.fixSketch);
|
|
2113
|
+
textViolation("verification", finding.verification);
|
|
2114
|
+
});
|
|
2115
|
+
return { ok: violations.length === 0, violations };
|
|
2116
|
+
}
|
|
1886
2117
|
function renderPlanFile(finding, plannedAt) {
|
|
1887
2118
|
const sections = [
|
|
1888
2119
|
`# ${finding.title}`,
|
|
@@ -1891,15 +2122,22 @@ function renderPlanFile(finding, plannedAt) {
|
|
|
1891
2122
|
`- **Priority**: ${finding.priority}`,
|
|
1892
2123
|
`- **Effort**: ${finding.effort}`,
|
|
1893
2124
|
`- **Risk**: ${finding.risk}`,
|
|
2125
|
+
...finding.confidence !== "MED" || hasEnrichedMetadata(finding) ? [`- **Confidence**: ${finding.confidence}`] : [],
|
|
2126
|
+
...finding.fingerprint !== undefined ? [`- **Fingerprint**: ${finding.fingerprint}`] : [],
|
|
2127
|
+
...finding.severity !== undefined ? [`- **Likelihood**: ${finding.severity.likelihood}`, `- **Severity impact**: ${finding.severity.impact}`, `- **Severity**: ${finding.severity.overall}`] : [],
|
|
1894
2128
|
`- **Depends on**: ${finding.dependsOn ?? "none"}`,
|
|
1895
2129
|
`- **Category**: ${finding.category}`,
|
|
2130
|
+
...finding.evidence.length > 0 ? [`- **Evidence**: ${collapseEvidenceWs(evidenceText(finding.evidence[0]))}`] : [],
|
|
1896
2131
|
`- **Planned at**: commit \`${plannedAt.commit}\`, ${plannedAt.date}`,
|
|
1897
2132
|
"",
|
|
1898
2133
|
"## Impact",
|
|
1899
2134
|
finding.impact
|
|
1900
2135
|
];
|
|
1901
2136
|
if (finding.evidence.length > 0) {
|
|
1902
|
-
sections.push("", "## Evidence", ...finding.evidence.map((
|
|
2137
|
+
sections.push("", "## Evidence", ...finding.evidence.map((item) => `- ${evidenceText(item)}`));
|
|
2138
|
+
}
|
|
2139
|
+
if (finding.trace !== undefined) {
|
|
2140
|
+
sections.push("", "## Trace", "", "| Kind | Location | Scope / Description |", "|------|----------|---------------------|", ...finding.trace.map((step) => `| ${escapeCell(step.kind)} | ${escapeCell(`${step.file}:${step.line}`)} | ${escapeCell(`${step.scope} — ${step.description}`).replace(/\r\n|\r|\n/g, "\\n")} |`));
|
|
1903
2141
|
}
|
|
1904
2142
|
if (finding.fixSketch !== undefined) {
|
|
1905
2143
|
sections.push("", "## Fix sketch", finding.fixSketch);
|
|
@@ -1919,7 +2157,8 @@ function redactFinding(finding) {
|
|
|
1919
2157
|
...finding,
|
|
1920
2158
|
title: redactText(finding.title),
|
|
1921
2159
|
impact: redactText(finding.impact),
|
|
1922
|
-
evidence: finding.evidence.map(redactText),
|
|
2160
|
+
evidence: finding.evidence.map((item) => typeof item === "string" ? redactText(item) : { ...item, description: redactText(item.description) }),
|
|
2161
|
+
...finding.trace !== undefined ? { trace: finding.trace.map((step) => ({ ...step, scope: redactText(step.scope), description: redactText(step.description) })) } : {},
|
|
1923
2162
|
...finding.fixSketch !== undefined ? { fixSketch: redactText(finding.fixSketch) } : {},
|
|
1924
2163
|
...finding.verification !== undefined ? { verification: redactText(finding.verification) } : {}
|
|
1925
2164
|
};
|
|
@@ -1941,7 +2180,10 @@ function extractSecurityDispositionSections(text) {
|
|
|
1941
2180
|
}
|
|
1942
2181
|
function renderIndex(params) {
|
|
1943
2182
|
const { date, repoName, repoShortSha, rows, rejected, needsVerification, hardeningChecked } = params;
|
|
1944
|
-
const
|
|
2183
|
+
const showFingerprint = rows.some((r) => r.fingerprint !== undefined);
|
|
2184
|
+
const showSeverity = rows.some((r) => r.likelihood !== undefined || r.severityImpact !== undefined || r.severity !== undefined);
|
|
2185
|
+
const cell = (value) => escapeCell(value ?? "—");
|
|
2186
|
+
const findingsRows = rows.map((r) => `| ${r.num} | ${escapeCell(r.title)} | ${r.category} | ${escapeCell(truncate(r.impact, 80))} | ${r.effort} | ${r.risk} | ${r.confidence} | ${escapeCell(truncate(r.evidence, 80))}` + (showFingerprint ? ` | ${cell(r.fingerprint)}` : "") + (showSeverity ? ` | ${cell(r.likelihood)} | ${cell(r.severityImpact)} | ${cell(r.severity)}` : "") + " |").join(`
|
|
1945
2187
|
`);
|
|
1946
2188
|
const directionRows = rows.filter((r) => r.category === "direction").map((r) => `- ${escapeCell(r.title)} — ${escapeCell(truncate(r.impact, 120))}`).join(`
|
|
1947
2189
|
`);
|
|
@@ -1954,8 +2196,8 @@ function renderIndex(params) {
|
|
|
1954
2196
|
"",
|
|
1955
2197
|
"## Findings",
|
|
1956
2198
|
"",
|
|
1957
|
-
"| # | Finding | Category | Impact | Effort | Risk | Confidence | Evidence |",
|
|
1958
|
-
"
|
|
2199
|
+
"| # | Finding | Category | Impact | Effort | Risk | Confidence | Evidence" + (showFingerprint ? " | Fingerprint" : "") + (showSeverity ? " | Likelihood | Severity impact | Severity" : "") + " |",
|
|
2200
|
+
"|---|---------|----------|--------|--------|------|------------|----------" + (showFingerprint ? "|------------" : "") + (showSeverity ? "|------------|-----------------|----------" : "") + "|",
|
|
1959
2201
|
findingsRows
|
|
1960
2202
|
];
|
|
1961
2203
|
if (directionRows !== "") {
|
|
@@ -1979,6 +2221,11 @@ function renderIndex(params) {
|
|
|
1979
2221
|
function scaffoldAuditPlan(outDir, findings, options = {}) {
|
|
1980
2222
|
const date = options.date ?? new Date().toISOString().slice(0, 10);
|
|
1981
2223
|
const plannedAt = options.plannedAt ?? { commit: options.repoShortSha ?? "unknown", date };
|
|
2224
|
+
const gate = validateAuditFindingGates(findings);
|
|
2225
|
+
if (!gate.ok) {
|
|
2226
|
+
const first = gate.violations[0];
|
|
2227
|
+
throw new TypeError(`invalid audit findings — ${first.code}: ${first.message}`);
|
|
2228
|
+
}
|
|
1982
2229
|
mkdirSync5(outDir, { recursive: true });
|
|
1983
2230
|
const existingReadme = join9(outDir, "README.md");
|
|
1984
2231
|
const carried = existsSync7(existingReadme) ? extractSecurityDispositionSections(readFileSync7(existingReadme, "utf8")) : { needsVerification: [], hardeningChecked: [] };
|
|
@@ -2013,10 +2260,14 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
|
|
|
2013
2260
|
impact: "see plan file",
|
|
2014
2261
|
effort: fields.get("Effort") ?? "—",
|
|
2015
2262
|
risk: fields.get("Risk") ?? "—",
|
|
2016
|
-
confidence: "—",
|
|
2263
|
+
confidence: fields.get("Confidence") ?? "—",
|
|
2017
2264
|
evidence: fields.get("Evidence") ?? "—",
|
|
2018
2265
|
priority: fields.get("Priority") ?? "—",
|
|
2019
|
-
dependsOn: fields.get("Depends on") ?? "—"
|
|
2266
|
+
dependsOn: fields.get("Depends on") ?? "—",
|
|
2267
|
+
fingerprint: fields.get("Fingerprint"),
|
|
2268
|
+
likelihood: fields.get("Likelihood"),
|
|
2269
|
+
severityImpact: fields.get("Severity impact"),
|
|
2270
|
+
severity: fields.get("Severity")
|
|
2020
2271
|
};
|
|
2021
2272
|
});
|
|
2022
2273
|
const byNum = new Map(rows.map((r) => [r.num, r]));
|
|
@@ -2031,9 +2282,13 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
|
|
|
2031
2282
|
row.effort = finding.effort;
|
|
2032
2283
|
row.risk = finding.risk;
|
|
2033
2284
|
row.confidence = finding.confidence;
|
|
2034
|
-
row.evidence = finding.evidence[0]
|
|
2285
|
+
row.evidence = finding.evidence.length > 0 ? collapseEvidenceWs(evidenceText(finding.evidence[0])) : "";
|
|
2035
2286
|
row.priority = finding.priority;
|
|
2036
2287
|
row.dependsOn = finding.dependsOn ?? "none";
|
|
2288
|
+
row.fingerprint = finding.fingerprint;
|
|
2289
|
+
row.likelihood = finding.severity?.likelihood;
|
|
2290
|
+
row.severityImpact = finding.severity?.impact;
|
|
2291
|
+
row.severity = finding.severity?.overall;
|
|
2037
2292
|
}
|
|
2038
2293
|
});
|
|
2039
2294
|
const needsVerificationLines = options.needsVerification !== undefined ? options.needsVerification.map((nv) => `- ${escapeCell(redactText(nv.lead))}: ${escapeCell(redactText(nv.how))}${nv.evidence ? ` (${escapeCell(redactText(nv.evidence))})` : ""}`) : carried.needsVerification;
|
|
@@ -2220,5 +2475,6 @@ export {
|
|
|
2220
2475
|
scaffoldAuditPlan,
|
|
2221
2476
|
scanSecrets,
|
|
2222
2477
|
supplyChainChecks,
|
|
2478
|
+
validateAuditFindingGates,
|
|
2223
2479
|
validateAuditStatusBlocks
|
|
2224
2480
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ValidationResult } from "./core.js";
|
|
2
2
|
/** Stable refusal codes of the scoped coordination surface (spec §C4). */
|
|
3
|
-
export declare const COORDINATION_ERROR_CODES: readonly ["coordination.harness-not-found", "coordination.workflow-not-found", "coordination.plan-not-found", "coordination.scope-mismatch", "coordination.path-mismatch", "coordination.assignment-invalid", "coordination.assignment-stale", "coordination.not-prepared", "coordination.duplicate-holder", "coordination.session-mismatch", "coordination.session-not-found", "coordination.session-role", "coordination.version-conflict", "coordination.expected-version-required", "coordination.invalid-transition", "coordination.invalid-input", "coordination.forbidden-field", "coordination.not-in-git", "coordination.git-unavailable", "coordination.git-proof", "coordination.evidence-stale", "coordination.integration-unresolved", "coordination.integration-diverged", "coordination.local-store-required", "coordination.direct-write-refused", "coordination.scoped-writer-required", "coordination.unknown-operation", "coordination.store", "coordination.prepare-amendment.stale", "coordination.prepare-amendment.invalid-patch", "coordination.prepare-amendment.not-prepare", "coordination.prepare-amendment.execution-started", "coordination.prepare-amendment.duplicate-plan", "coordination.prepare-amendment.invalid-plan", "coordination.prepare-amendment.compass-mismatch", "coordination.prepare-amendment.invalid-worktree"];
|
|
3
|
+
export declare const COORDINATION_ERROR_CODES: readonly ["coordination.harness-not-found", "coordination.workflow-not-found", "coordination.plan-not-found", "coordination.scope-mismatch", "coordination.path-mismatch", "coordination.assignment-invalid", "coordination.assignment-stale", "coordination.not-prepared", "coordination.duplicate-holder", "coordination.session-mismatch", "coordination.session-not-found", "coordination.session-role", "coordination.version-conflict", "coordination.expected-version-required", "coordination.invalid-transition", "coordination.invalid-input", "coordination.forbidden-field", "coordination.not-in-git", "coordination.git-unavailable", "coordination.git-proof", "coordination.evidence-stale", "coordination.integration-unresolved", "coordination.integration-diverged", "coordination.local-store-required", "coordination.direct-write-refused", "coordination.scoped-writer-required", "coordination.unknown-operation", "coordination.store", "coordination.prepare-amendment.stale", "coordination.prepare-amendment.invalid-patch", "coordination.prepare-amendment.not-prepare", "coordination.prepare-amendment.execution-started", "coordination.prepare-amendment.duplicate-plan", "coordination.prepare-amendment.invalid-plan", "coordination.prepare-amendment.compass-mismatch", "coordination.prepare-amendment.invalid-worktree", "coordination.delivery-source-repair.unsupported-workflow", "coordination.delivery-source-repair.terminal", "coordination.delivery-source-repair.no-accepted-handoff", "coordination.delivery-source-repair.already-aligned", "coordination.delivery-source-repair.not-legacy-shape", "coordination.delivery-source-repair.pr-conflict"];
|
|
4
4
|
export type CoordinationErrorCode = (typeof COORDINATION_ERROR_CODES)[number];
|
|
5
5
|
/**
|
|
6
6
|
* Stable exception of the coordination surface: `code` is the consumer
|
|
@@ -156,12 +156,14 @@ export declare const HANDOFF_STATES: readonly HandoffState[];
|
|
|
156
156
|
export declare const PLAN_PROGRESS_STATUSES: readonly PlanProgressStatus[];
|
|
157
157
|
/** Validate a stored `PlanProgress` (`status`, `summary`, `evidence_paths`, `track_branches`). */
|
|
158
158
|
export declare function validatePlanProgress(value: unknown, what?: string): ValidationResult[];
|
|
159
|
+
/** Route-aware stored handoff validation (spec A5). Default remains strict integration. */
|
|
160
|
+
export type RowValidationRoute = "integration" | "standalone-development";
|
|
159
161
|
/** Validate a stored `PlanHandoff`, including its state/field coherence. */
|
|
160
|
-
export declare function validatePlanHandoff(value: unknown, what?: string): ValidationResult[];
|
|
162
|
+
export declare function validatePlanHandoff(value: unknown, what?: string, route?: RowValidationRoute): ValidationResult[];
|
|
161
163
|
/** Validate a stored `PreparedCoordination`. */
|
|
162
164
|
export declare function validatePreparedCoordination(value: unknown, what?: string): ValidationResult[];
|
|
163
165
|
/** Validate one plan row's `coordination` object (spec §C2). */
|
|
164
|
-
export declare function validateRowCoordination(value: unknown, what?: string): ValidationResult[];
|
|
166
|
+
export declare function validateRowCoordination(value: unknown, what?: string, route?: RowValidationRoute): ValidationResult[];
|
|
165
167
|
/** Validate a snapshot's top `coordination` block (spec §C2). */
|
|
166
168
|
export declare function validateSnapshotCoordination(value: unknown, what?: string): ValidationResult[];
|
|
167
169
|
/** Hash-pinned evidence reference for an absolute path, read from disk. */
|
package/dist/coordination.d.ts
CHANGED
|
@@ -197,6 +197,9 @@ export type PlanCoordinationOperation = {
|
|
|
197
197
|
} | {
|
|
198
198
|
kind: "complete";
|
|
199
199
|
handoffId: string;
|
|
200
|
+
} | {
|
|
201
|
+
kind: "repair-delivery-source";
|
|
202
|
+
handoffId: string;
|
|
200
203
|
} | {
|
|
201
204
|
kind: "reconcile";
|
|
202
205
|
handoffId: string;
|
|
@@ -272,6 +275,8 @@ export declare function bindPlanSession(input: BindPlanSessionInput): Promise<Co
|
|
|
272
275
|
* an unknown key anywhere is rejected before any state is touched.
|
|
273
276
|
*/
|
|
274
277
|
export declare function mutatePlanCoordination(request: CoordinationRequest): Promise<CoordinationResult>;
|
|
278
|
+
/** Test-only hook to observe the precheck→mutate gap in standalone complete. */
|
|
279
|
+
export declare function setCompleteStandaloneMutateGapForTest(callback: (() => void) | undefined): void;
|
|
275
280
|
/**
|
|
276
281
|
* Replace a coordinated artifact with an exact-version precondition (spec §B,
|
|
277
282
|
* §C4 line 156). Snapshot replacement goes through the canonical snapshot
|