122 lines
4.0 KiB
TypeScript
122 lines
4.0 KiB
TypeScript
import type { BinarySessionPort } from "../../application/BinarySession.js";
|
|
import type { Evidence } from "../../domain/evidence.js";
|
|
import {
|
|
EvidenceIntegrityError,
|
|
type AnalysisError,
|
|
} from "../../domain/errors.js";
|
|
import { err, ok, type Result } from "../../domain/result.js";
|
|
import { recordDerivedEvidence } from "../recordDerivedEvidence.js";
|
|
import type { WorkflowUnknownInput } from "./types.js";
|
|
import { evaluateReconstructionClosure } from "../../domain/reconstructionCoverage.js";
|
|
import type { z } from "zod";
|
|
import type { reconstructionVerificationInputSchema } from "../../domain/reconstructionVerification.js";
|
|
import { AnalysisInputError } from "../../domain/errors.js";
|
|
|
|
export const isIncomplete = (status: string): boolean =>
|
|
status === "unknown" || status === "truncated";
|
|
|
|
export const comparisonClosure = (comparisons: readonly Evidence[]): string[] =>
|
|
uniqueIds(
|
|
comparisons.flatMap((evidence) => [
|
|
evidence.evidence_id,
|
|
...evidence.evidence_links,
|
|
]),
|
|
);
|
|
|
|
export const functionEvidenceIds = (records: readonly Evidence[]): string[] =>
|
|
uniqueIds(records.map(({ evidence_id }) => evidence_id));
|
|
|
|
const uniqueIds = (ids: readonly string[]): string[] =>
|
|
[...new Set(ids)].sort((left, right) => left.localeCompare(right));
|
|
|
|
export const evidenceClosure = (
|
|
session: BinarySessionPort,
|
|
seedIds: readonly string[],
|
|
): Result<string[], EvidenceIntegrityError> => {
|
|
const records = new Map(
|
|
session
|
|
.exportEvidenceBundle()
|
|
.records.map((evidence) => [evidence.evidence_id, evidence]),
|
|
);
|
|
const visited = new Set<string>();
|
|
const pending = [...seedIds];
|
|
while (pending.length > 0) {
|
|
const evidenceId = pending.pop();
|
|
if (evidenceId === undefined || visited.has(evidenceId)) continue;
|
|
const evidence = records.get(evidenceId);
|
|
if (evidence === undefined)
|
|
return err(
|
|
new EvidenceIntegrityError(
|
|
"Investigation input has a dangling Evidence link",
|
|
),
|
|
);
|
|
visited.add(evidenceId);
|
|
pending.push(...evidence.evidence_links);
|
|
}
|
|
return ok(uniqueIds([...visited]));
|
|
};
|
|
|
|
/** Verify a retained coverage boundary before reconstruction evidence is derived. */
|
|
export const verifyCoverageReadiness = (
|
|
session: BinarySessionPort,
|
|
coverage: z.output<typeof reconstructionVerificationInputSchema>["coverage"],
|
|
): Result<null, AnalysisError> => {
|
|
if (coverage === undefined) return ok(null);
|
|
const workspace = session.reconstructionCoverageWorkspace(
|
|
coverage.workspace_id,
|
|
coverage.revision,
|
|
);
|
|
if (
|
|
workspace === undefined ||
|
|
workspace.revision_sha256 !== coverage.revision_sha256
|
|
)
|
|
return err(
|
|
new EvidenceIntegrityError(
|
|
"The requested reconstruction coverage revision is not retained by this session",
|
|
),
|
|
);
|
|
try {
|
|
const result = evaluateReconstructionClosure(
|
|
workspace,
|
|
coverage.boundary_id,
|
|
Date.now(),
|
|
);
|
|
return result.status === "ready"
|
|
? ok(null)
|
|
: err(
|
|
new EvidenceIntegrityError(
|
|
`Reconstruction coverage boundary is ${result.status}; readiness requires ready`,
|
|
),
|
|
);
|
|
} catch (cause: unknown) {
|
|
return err(new AnalysisInputError("verify_reconstruction", { cause }));
|
|
}
|
|
};
|
|
|
|
export const recordWorkflowEvidence = (
|
|
...[session, evidence, approved, unresolved, input]: readonly [
|
|
session: BinarySessionPort,
|
|
evidence: Evidence,
|
|
approved: true | undefined,
|
|
unresolved: boolean,
|
|
input: WorkflowUnknownInput,
|
|
]
|
|
): Result<Evidence, AnalysisError> => {
|
|
if (approved !== true || !unresolved) {
|
|
return recordDerivedEvidence(session, evidence, undefined);
|
|
}
|
|
return recordDerivedEvidence(session, evidence, {
|
|
approved: true,
|
|
question: input.question,
|
|
severity: "high",
|
|
domain: input.domain,
|
|
supporting_evidence_ids: [evidence.evidence_id],
|
|
contradicting_evidence_ids: [],
|
|
required_authority: input.requiredAuthority,
|
|
required_confidence: input.requiredConfidence,
|
|
required_environment: null,
|
|
recommended_probes: [...input.probes],
|
|
relationships: [],
|
|
});
|
|
};
|