192 lines
6.4 KiB
TypeScript
192 lines
6.4 KiB
TypeScript
import {
|
|
JAVASCRIPT_APPLICATION_PROVIDER,
|
|
JAVASCRIPT_RUNTIME_RECONCILIATION_PROVIDER,
|
|
MANAGED_WORKFLOW_PROVIDER,
|
|
} from "../InvestigationProviders.js";
|
|
import { parseEvidence, type Evidence } from "../../domain/evidence.js";
|
|
import {
|
|
analyzeJavaScriptApplicationInputSchema,
|
|
javascriptApplicationAnalysisResultSchema,
|
|
type JavaScriptApplicationAnalysisResult,
|
|
} from "../../domain/javascript/javascriptApplicationAnalysis.js";
|
|
import type { JavaScriptApplicationGraph } from "../../domain/javascript/javascriptApplicationGraph.js";
|
|
import type { JavaScriptSemanticGraph } from "../../domain/javascript/javascriptSemanticGraph.js";
|
|
import { javascriptRuntimeReconciliationResultSchema } from "../../domain/javascript/javascriptRuntimeReconciliationSchemas.js";
|
|
import { managedApplicationGraphResultSchema } from "../../domain/managedApplicationGraph.js";
|
|
|
|
/** Supported immutable source for an application-level graph workflow. */
|
|
export interface ApplicationGraphEvidenceSource {
|
|
readonly evidence: Evidence;
|
|
readonly graph: JavaScriptApplicationGraph;
|
|
readonly kind:
|
|
| "static-application"
|
|
| "static-runtime-reconciliation"
|
|
| "managed-application";
|
|
readonly rootArtifactSha256: string;
|
|
readonly semanticGraph: JavaScriptSemanticGraph | null;
|
|
}
|
|
|
|
/** Parse and authenticate one REA-produced JavaScript Application Graph Evidence. */
|
|
export const parseApplicationGraphEvidence = (
|
|
input: unknown,
|
|
): ApplicationGraphEvidenceSource => {
|
|
const evidence = parseEvidence(input);
|
|
if (
|
|
evidence.operation === "analyze_javascript_application" &&
|
|
evidence.predicate_type === "rea.javascript-application-analysis" &&
|
|
providerMatches(evidence, JAVASCRIPT_APPLICATION_PROVIDER)
|
|
)
|
|
return staticApplicationSource(evidence);
|
|
if (
|
|
evidence.operation === "reconcile_javascript_runtime" &&
|
|
evidence.predicate_type === "rea.javascript-runtime-reconciliation" &&
|
|
providerMatches(evidence, JAVASCRIPT_RUNTIME_RECONCILIATION_PROVIDER)
|
|
)
|
|
return staticRuntimeSource(evidence);
|
|
if (
|
|
evidence.operation === "project_managed_application_graph" &&
|
|
evidence.predicate_type === "rea.managed-application-graph" &&
|
|
providerMatches(evidence, MANAGED_WORKFLOW_PROVIDER)
|
|
)
|
|
return managedApplicationSource(evidence);
|
|
throw new TypeError(
|
|
"Application workflow requires authenticated analyze_javascript_application, reconcile_javascript_runtime, or project_managed_application_graph Evidence",
|
|
);
|
|
};
|
|
|
|
const staticApplicationSource = (
|
|
evidence: Evidence,
|
|
): ApplicationGraphEvidenceSource => {
|
|
if (
|
|
evidence.authority !== "shipped-artifact" ||
|
|
evidence.confidence !== "derived"
|
|
)
|
|
throw new TypeError(
|
|
"JavaScript application Evidence authority or confidence is invalid",
|
|
);
|
|
const result = javascriptApplicationAnalysisResultSchema.parse(
|
|
evidence.normalized_result,
|
|
);
|
|
if (evidence.predicate_type !== "rea.javascript-application-analysis")
|
|
throw new TypeError(
|
|
"JavaScript application Evidence predicate does not match its result shape",
|
|
);
|
|
analyzeJavaScriptApplicationInputSchema.parse({
|
|
input_path: result.input_path,
|
|
...evidence.parameters,
|
|
});
|
|
assertApplicationSubject(evidence, result);
|
|
return {
|
|
evidence,
|
|
graph: result.graph,
|
|
kind: "static-application",
|
|
rootArtifactSha256: result.root_artifact_sha256,
|
|
semanticGraph: result.semantic_graph,
|
|
};
|
|
};
|
|
|
|
const staticRuntimeSource = (
|
|
evidence: Evidence,
|
|
): ApplicationGraphEvidenceSource => {
|
|
if (
|
|
evidence.authority !== "analyst-inference" ||
|
|
evidence.confidence !== "inferred"
|
|
)
|
|
throw new TypeError(
|
|
"JavaScript runtime reconciliation Evidence authority or confidence is invalid",
|
|
);
|
|
const result = javascriptRuntimeReconciliationResultSchema.parse(
|
|
evidence.normalized_result,
|
|
);
|
|
if (
|
|
result.evidence_links.some(
|
|
(evidenceId) => !evidence.evidence_links.includes(evidenceId),
|
|
)
|
|
)
|
|
throw new TypeError(
|
|
"Runtime reconciliation result references Evidence outside its envelope",
|
|
);
|
|
const applicationLayer = result.static_layers.find(
|
|
({ role }) => role === "application",
|
|
);
|
|
if (applicationLayer === undefined)
|
|
throw new TypeError("Runtime reconciliation application layer is missing");
|
|
return {
|
|
evidence,
|
|
graph: result.graph,
|
|
kind: "static-runtime-reconciliation",
|
|
rootArtifactSha256: applicationLayer.root_artifact_sha256,
|
|
semanticGraph: null,
|
|
};
|
|
};
|
|
|
|
const managedApplicationSource = (
|
|
evidence: Evidence,
|
|
): ApplicationGraphEvidenceSource => {
|
|
if (
|
|
evidence.authority !== "analyst-inference" ||
|
|
evidence.confidence !== "inferred"
|
|
)
|
|
throw new TypeError(
|
|
"Managed application graph Evidence authority or confidence is invalid",
|
|
);
|
|
const result = managedApplicationGraphResultSchema.parse(
|
|
evidence.normalized_result,
|
|
);
|
|
if (
|
|
result.evidence_links.some(
|
|
(evidenceId) => !evidence.evidence_links.includes(evidenceId),
|
|
)
|
|
)
|
|
throw new TypeError(
|
|
"Managed application graph result references Evidence outside its envelope",
|
|
);
|
|
return {
|
|
evidence,
|
|
graph: result.graph,
|
|
kind: "managed-application",
|
|
rootArtifactSha256: result.root_artifact_sha256,
|
|
semanticGraph: null,
|
|
};
|
|
};
|
|
|
|
const assertApplicationSubject = (
|
|
evidence: Evidence,
|
|
result: JavaScriptApplicationAnalysisResult,
|
|
): void => {
|
|
if (
|
|
evidence.subject === null ||
|
|
evidence.subject.digest.sha256 !== result.root_artifact_sha256 ||
|
|
evidence.subject.format !== result.format ||
|
|
evidence.subject.local_path !== result.input_path
|
|
)
|
|
throw new TypeError(
|
|
"JavaScript application Evidence subject does not match its result",
|
|
);
|
|
};
|
|
|
|
const providerMatches = (
|
|
evidence: Evidence,
|
|
provider: {
|
|
readonly id: string;
|
|
readonly name: string;
|
|
readonly version: string;
|
|
},
|
|
): boolean =>
|
|
evidence.provider.id === provider.id &&
|
|
evidence.provider.name === provider.name &&
|
|
evidence.provider.version === provider.version;
|
|
|
|
/** Parse unique, artifact-bound Evidence that may extend a native handoff. */
|
|
export const parseNativeApplicationEvidence = (
|
|
inputs: readonly unknown[],
|
|
): Evidence[] => {
|
|
const parsed = inputs.map((input) => parseEvidence(input));
|
|
if (parsed.some(({ subject }) => subject === null))
|
|
throw new TypeError("Native handoff Evidence requires an artifact subject");
|
|
const ids = parsed.map(({ evidence_id: id }) => id);
|
|
if (new Set(ids).size !== ids.length)
|
|
throw new TypeError("Native handoff Evidence must be unique");
|
|
return parsed;
|
|
};
|