Narrow WASM and EVM execution ports to file subjects and preserve supplied subject types at the shared execution factory. Authenticate format with path and digest before recording or projecting Evidence, using the existing WASM identity owner in both consumers. Validation: focused WASM/EVM service and public MCP workflows passed, including malformed provider subjects and self-contained retained Evidence with contradictory format. Global typecheck/lint and documentation checks passed. Real WABT/Evmole execution and the full suite were not run.
147 lines
5.0 KiB
TypeScript
147 lines
5.0 KiB
TypeScript
import { z } from "zod";
|
|
import type { AnalysisError } from "../analysisErrorBase.js";
|
|
import { uniqueSorted } from "../canonicalOrdering.js";
|
|
import { evmInterfaceSchema } from "../evm/evmInterface.js";
|
|
import { analysisInputErrorFromIssues } from "../inputIssueProjection.js";
|
|
import { err, ok, type Result } from "../result.js";
|
|
import {
|
|
analysisViewInputError,
|
|
type AnalysisViewParent,
|
|
type AnalysisViewRequest,
|
|
type UnsignedAnalysisView,
|
|
} from "./analysisView.js";
|
|
import {
|
|
completeWithinViewCoverage,
|
|
pageViewCoverage,
|
|
} from "./analysisViewCoverage.js";
|
|
import { analysisTextPage } from "./analysisViewText.js";
|
|
|
|
/** Retained inferred candidates or exact text; offsets for text count UTF-16 code units. */
|
|
export const evmViewRequestSchema = z.strictObject({
|
|
kind: z.literal("evm"),
|
|
facet: z.enum(["functions", "bytecode_hex", "stdout", "stderr"]),
|
|
offset: z.number().int().nonnegative().default(0),
|
|
limit: z.number().int().positive().default(64),
|
|
});
|
|
|
|
export const evmViewItemSchema = z.union([
|
|
evmInterfaceSchema.shape.functions,
|
|
z.strictObject({ text: z.string(), unit: z.literal("utf16-code-units") }),
|
|
]);
|
|
|
|
/** Complete inferred interface facts without the unrequested hex or duplicated stdout. */
|
|
export const evmSummarySchema = z.strictObject({
|
|
artifact: evmInterfaceSchema.shape.artifact,
|
|
bytecode: evmInterfaceSchema.shape.bytecode.omit({ hex: true }),
|
|
evidence_kind: evmInterfaceSchema.shape.evidence_kind,
|
|
functions: evmInterfaceSchema.shape.functions,
|
|
discovery_completeness: evmInterfaceSchema.shape.discovery_completeness,
|
|
runtime_execution: evmInterfaceSchema.shape.runtime_execution,
|
|
diagnostics: evmInterfaceSchema.shape.diagnostics
|
|
.omit({ stdout: true })
|
|
.extend({
|
|
stdout_code_units: z.number().int().nonnegative(),
|
|
}),
|
|
limitations: evmInterfaceSchema.shape.limitations,
|
|
});
|
|
|
|
/** Select already authenticated producer facts; no interpreter or provider is invoked. */
|
|
export const projectEvmInterfaceView = (
|
|
parent: AnalysisViewParent,
|
|
view: AnalysisViewRequest,
|
|
): Result<UnsignedAnalysisView, AnalysisError> => {
|
|
if (view.kind !== "summary" && view.kind !== "evm")
|
|
return err(
|
|
analysisViewInputError([
|
|
{
|
|
path: ["view"],
|
|
reason: "invalid_value",
|
|
message: "EVM Evidence supports summary and evm views.",
|
|
},
|
|
]),
|
|
);
|
|
const parsed = evmInterfaceSchema.safeParse(parent.normalizedResult);
|
|
if (!parsed.success)
|
|
return err(
|
|
analysisInputErrorFromIssues(
|
|
"inspect_analysis_view",
|
|
parsed.error.issues.map((issue) => ({
|
|
...issue,
|
|
path: ["source", "normalized_result", ...issue.path],
|
|
})),
|
|
{ source: { normalized_result: parent.normalizedResult } },
|
|
{ cause: parsed.error },
|
|
),
|
|
);
|
|
const report = parsed.data;
|
|
if (
|
|
parent.subject == null ||
|
|
parent.subject.format !== "file" ||
|
|
parent.subject.local_path !== report.artifact.path ||
|
|
parent.subject.digest.sha256 !== report.artifact.sha256
|
|
)
|
|
return err(
|
|
analysisViewInputError([
|
|
{
|
|
path: ["source", "subject"],
|
|
reason: "invalid_value",
|
|
message:
|
|
"EVM parent subject must identify the report's exact carrier path and SHA-256.",
|
|
},
|
|
]),
|
|
);
|
|
const shared = {
|
|
parent_evidence_id: parent.evidenceId,
|
|
parent_operation: "inspect_evm_interface" as const,
|
|
parent_digest: parent.evidenceId.slice(3),
|
|
artifact: { path: report.artifact.path, sha256: report.artifact.sha256 },
|
|
limitations: uniqueSorted([...parent.limitations, ...report.limitations]),
|
|
unknowns: [
|
|
"Candidates remain inferred; discovery completeness, bytecode kind, hardfork and deployment authenticity are unknown. Runtime execution was not performed.",
|
|
],
|
|
};
|
|
if (view.kind === "summary") {
|
|
const { hex: _hex, ...bytecode } = report.bytecode;
|
|
const { stdout, ...diagnostics } = report.diagnostics;
|
|
return ok({
|
|
kind: "summary",
|
|
view,
|
|
...shared,
|
|
summary: {
|
|
artifact: report.artifact,
|
|
bytecode,
|
|
evidence_kind: report.evidence_kind,
|
|
functions: report.functions,
|
|
discovery_completeness: report.discovery_completeness,
|
|
runtime_execution: report.runtime_execution,
|
|
diagnostics: { ...diagnostics, stdout_code_units: stdout.length },
|
|
limitations: report.limitations,
|
|
},
|
|
coverage: completeWithinViewCoverage(
|
|
report.functions.length,
|
|
report.functions.length,
|
|
),
|
|
});
|
|
}
|
|
if (view.facet === "functions") {
|
|
const item = report.functions.slice(view.offset, view.offset + view.limit);
|
|
return ok({
|
|
kind: "evm",
|
|
view,
|
|
item,
|
|
...shared,
|
|
coverage: pageViewCoverage(
|
|
view.offset,
|
|
item.length,
|
|
report.functions.length,
|
|
),
|
|
});
|
|
}
|
|
const text =
|
|
view.facet === "bytecode_hex"
|
|
? report.bytecode.hex
|
|
: report.diagnostics[view.facet];
|
|
const page = analysisTextPage(text, view.offset, view.limit, view.facet);
|
|
return page.ok ? ok({ kind: "evm", view, ...page.value, ...shared }) : page;
|
|
};
|