mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 17:30:32 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14e2971eb9 | ||
|
|
d3a97d54e8 | ||
|
|
68df93fd28 |
No files matched your search
@@ -369,10 +369,7 @@ export class TsharkCaptureProvider implements PacketCapturePort {
|
||||
resourcePath = "path" in input ? input.path : command;
|
||||
// TShark resolves `-e` aliases before emitting canonical field names;
|
||||
// the command arguments, not output names, own field selection.
|
||||
const matchingPackets = parsePacketOutput(
|
||||
output.stdout.text,
|
||||
input.packet_limit,
|
||||
);
|
||||
const matchingPackets = parsePacketOutput(output.stdout.text);
|
||||
const start = Math.min(input.match_offset, matchingPackets.length);
|
||||
const end =
|
||||
input.match_limit === null
|
||||
|
||||
@@ -6,7 +6,7 @@ import { packetFieldsSchema } from "../domain/captures/packetCapture.js";
|
||||
const producerSchema = z.array(
|
||||
z.object({ _source: z.object({ layers: packetFieldsSchema }) }),
|
||||
);
|
||||
export const parsePacketOutput = (text: string, packetLimit: number | null) => {
|
||||
export const parsePacketOutput = (text: string) => {
|
||||
const decoded: unknown = JSON.parse(text);
|
||||
const packets = producerSchema
|
||||
.parse(decoded)
|
||||
@@ -19,11 +19,6 @@ export const parsePacketOutput = (text: string, packetLimit: number | null) => {
|
||||
"inspect_packet_capture",
|
||||
"TShark returned unordered frames.",
|
||||
);
|
||||
if (packetLimit !== null && frame > BigInt(packetLimit))
|
||||
throw new AnalysisOutputError(
|
||||
"inspect_packet_capture",
|
||||
"TShark returned a frame beyond the selected input packet limit.",
|
||||
);
|
||||
previous = frame;
|
||||
}
|
||||
return packets;
|
||||
|
||||
@@ -559,6 +559,7 @@ export const erlangBeamSummaryViewResultSchema = z.strictObject({
|
||||
});
|
||||
export const erlangBeamTableViewResultSchema = z.strictObject({
|
||||
...viewResultBase,
|
||||
coverage: nullableCollectionViewCoverageSchema,
|
||||
kind: z.literal("beam"),
|
||||
parent_operation: z.literal("inspect_erlang_beam"),
|
||||
view: erlangBeamViewRequestSchema,
|
||||
@@ -624,6 +625,7 @@ export const rustBinarySummaryViewResultSchema = z.strictObject({
|
||||
|
||||
export const rustBinaryPageViewResultSchema = z.strictObject({
|
||||
...viewResultBase,
|
||||
coverage: nullableCollectionViewCoverageSchema,
|
||||
...rustBinaryViewContextSchema.shape,
|
||||
kind: z.literal("rust"),
|
||||
parent_operation: z.literal("inspect_rust_binary"),
|
||||
@@ -1505,6 +1507,7 @@ export const analysisViewResultSchema = z.discriminatedUnion("kind", [
|
||||
z.strictObject({
|
||||
...viewResultBase,
|
||||
...goBinaryViewContextSchema.shape,
|
||||
coverage: nullableCollectionViewCoverageSchema,
|
||||
kind: z.literal("go"),
|
||||
parent_operation: z.literal("inspect_go_binary"),
|
||||
view: goBinaryViewRequestSchema,
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { createEvidence } from "../evidence.js";
|
||||
import type { ErlangBeam } from "../erlang/beam.js";
|
||||
import type { ModelArtifact } from "../model/modelArtifact.js";
|
||||
import {
|
||||
projectAnalysisView,
|
||||
type AnalysisViewParent,
|
||||
} from "./analysisView.js";
|
||||
|
||||
const parent = (
|
||||
operation: string,
|
||||
result: ErlangBeam | ModelArtifact,
|
||||
): AnalysisViewParent => {
|
||||
const evidence = createEvidence(
|
||||
{ path: result.artifact.path, sha256: "a".repeat(64), format: "file" },
|
||||
{ id: "fixture", name: "Fixture reader", version: "1" },
|
||||
{ operation, parameters: {}, result },
|
||||
);
|
||||
return {
|
||||
predicateType: evidence.predicate_type,
|
||||
evidenceId: evidence.evidence_id,
|
||||
operation,
|
||||
subject: evidence.subject,
|
||||
normalizedResult: result,
|
||||
limitations: [],
|
||||
};
|
||||
};
|
||||
|
||||
const beam: ErlangBeam = {
|
||||
artifact: {
|
||||
path: "/artifacts/module.beam",
|
||||
sha256: "a".repeat(64),
|
||||
bytes: 12,
|
||||
},
|
||||
form: { location: { offset: 0, bytes: 12 }, size: 4 },
|
||||
chunks: [],
|
||||
atoms: null,
|
||||
atom_encoding: null,
|
||||
exports: null,
|
||||
imports: null,
|
||||
lambdas: null,
|
||||
code: null,
|
||||
module: null,
|
||||
coverage: { status: "complete", examined_chunks: 0, atoms: 0 },
|
||||
limitations: [],
|
||||
};
|
||||
|
||||
it.each(["atoms", "exports", "imports", "lambdas"] as const)(
|
||||
"distinguishes unavailable BEAM %s from a decoded empty table",
|
||||
(collection) => {
|
||||
for (const available of [false, true]) {
|
||||
const result = projectAnalysisView(
|
||||
parent("inspect_erlang_beam", {
|
||||
...beam,
|
||||
[collection]: available ? [] : null,
|
||||
}),
|
||||
{ kind: "beam", collection, offset: 7, limit: 1 },
|
||||
);
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.coverage).toEqual({
|
||||
status: available ? "empty" : "unavailable",
|
||||
examined: 0,
|
||||
total: available ? 0 : null,
|
||||
next_offset: null,
|
||||
exhausted: available,
|
||||
});
|
||||
expect(result.value).toMatchObject({ page: { collection, items: [] } });
|
||||
if (!available)
|
||||
expect(result.value.unknowns.join(" ")).toContain(
|
||||
`No ${collection} table was decoded`,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const model: ModelArtifact = {
|
||||
artifact: {
|
||||
path: "/artifacts/model.gguf",
|
||||
file_size: 32,
|
||||
header_sha256: "a".repeat(64),
|
||||
header_length: 32,
|
||||
sha256: "a".repeat(64),
|
||||
bytes_read: 32,
|
||||
read_scope: "full-file",
|
||||
},
|
||||
format: "gguf",
|
||||
version: 3,
|
||||
byte_order: "little",
|
||||
alignment: 32,
|
||||
tensor_data_offset: 32,
|
||||
metadata: [],
|
||||
tensors: [],
|
||||
totals: {
|
||||
confidence: "derived",
|
||||
parameter_count: "0",
|
||||
tensor_bytes: 0,
|
||||
by_type: [],
|
||||
},
|
||||
coverage: {
|
||||
header: "complete",
|
||||
tensor_data: "not-interpreted",
|
||||
array_values: "preview",
|
||||
tensor_ranges: "partial",
|
||||
storage_sizes: "partial",
|
||||
},
|
||||
limitations: [
|
||||
"Array metadata retains only the requested preview; storage sizes remain unknown.",
|
||||
],
|
||||
};
|
||||
|
||||
it.each(["metadata", "tensors", "types"] as const)(
|
||||
"retains producer limitations on model %s pages with no envelope warnings",
|
||||
(collection) => {
|
||||
const input = parent("inspect_model_artifact", model);
|
||||
const result = projectAnalysisView(input, {
|
||||
kind: "model-artifact",
|
||||
collection,
|
||||
offset: 0,
|
||||
limit: 1,
|
||||
});
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.limitations).toEqual(model.limitations);
|
||||
expect(input.limitations).toEqual([]);
|
||||
},
|
||||
);
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
boundedViewMessages,
|
||||
completeWithinViewCoverage,
|
||||
pageViewCoverage,
|
||||
unavailableViewCoverage,
|
||||
} from "./analysisViewCoverage.js";
|
||||
import { selectViewRows } from "./selectViewRows.js";
|
||||
|
||||
@@ -120,7 +121,7 @@ type Row = NonNullable<ErlangBeam[Table]>[number];
|
||||
type BeamView = z.infer<typeof erlangBeamViewRequestSchema>;
|
||||
|
||||
/** Select already parsed rows; original atom/lambda indexes are never repurposed. */
|
||||
const selectRows = (rows: readonly Row[] | null, view: BeamView) => {
|
||||
const selectRows = (source: readonly Row[], view: BeamView) => {
|
||||
const names = view.names === undefined ? undefined : new Set(view.names);
|
||||
const indices =
|
||||
view.indices === undefined ? undefined : new Set(view.indices);
|
||||
@@ -138,7 +139,6 @@ const selectRows = (rows: readonly Row[] | null, view: BeamView) => {
|
||||
),
|
||||
source_index,
|
||||
});
|
||||
const source = rows ?? [];
|
||||
if (names === undefined && indices === undefined && modules === undefined)
|
||||
return {
|
||||
rows: source
|
||||
@@ -223,7 +223,17 @@ export const projectErlangBeamView = (
|
||||
summary,
|
||||
coverage: completeWithinViewCoverage(chunks.length, chunks.length),
|
||||
});
|
||||
const selected = selectRows(parsed.value[view.collection], view);
|
||||
const rows = parsed.value[view.collection];
|
||||
if (rows === null)
|
||||
return ok({
|
||||
...shared,
|
||||
kind: "beam" as const,
|
||||
view,
|
||||
beam: summary,
|
||||
page: { collection: view.collection, items: [] },
|
||||
coverage: unavailableViewCoverage(),
|
||||
});
|
||||
const selected = selectRows(rows, view);
|
||||
return ok({
|
||||
...shared,
|
||||
kind: "beam" as const,
|
||||
|
||||
@@ -238,3 +238,53 @@ it("refuses string_literals view when unrequested and projects filtered/truncate
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["dependencies", "settings"] as const)(
|
||||
"distinguishes unavailable Go %s from decoded empty records",
|
||||
(collection) => {
|
||||
for (const available of [false, true]) {
|
||||
const input = parent();
|
||||
const result = projectAnalysisView(
|
||||
{
|
||||
...input,
|
||||
normalizedResult: {
|
||||
...report,
|
||||
build_info: available
|
||||
? {
|
||||
header_offset: 0,
|
||||
encoding: "inline",
|
||||
go_version: null,
|
||||
go_version_bytes_base64: "",
|
||||
module_text: null,
|
||||
module_bytes_base64: "",
|
||||
version_location: { offset: 32, bytes: 0 },
|
||||
module_location: { offset: 32, bytes: 0 },
|
||||
module: {
|
||||
path: null,
|
||||
main: null,
|
||||
dependencies: [],
|
||||
settings: [],
|
||||
unparsed_lines: [],
|
||||
unparsed_line_bytes_base64: [],
|
||||
complete: true,
|
||||
},
|
||||
}
|
||||
: null,
|
||||
},
|
||||
},
|
||||
{ kind: "go", collection, offset: 3, limit: 1 },
|
||||
);
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.coverage).toEqual({
|
||||
status: available ? "empty" : "unavailable",
|
||||
examined: 0,
|
||||
total: available ? 0 : null,
|
||||
next_offset: null,
|
||||
exhausted: available,
|
||||
});
|
||||
expect(result.value).toMatchObject({
|
||||
source_coverage: { build_info_available: available },
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
boundedViewMessages,
|
||||
completeWithinViewCoverage,
|
||||
pageViewCoverage,
|
||||
unavailableViewCoverage,
|
||||
} from "./analysisViewCoverage.js";
|
||||
import { analysisViewInputError } from "./analysisViewErrors.js";
|
||||
import { createAnalysisViewResultReader } from "./analysisViewParent.js";
|
||||
@@ -322,10 +323,18 @@ export const projectGoBinaryView = (
|
||||
),
|
||||
});
|
||||
}
|
||||
if (info === null)
|
||||
return ok({
|
||||
kind: "go" as const,
|
||||
view,
|
||||
...shared,
|
||||
page: { collection: view.collection, rows: [] },
|
||||
coverage: unavailableViewCoverage(),
|
||||
});
|
||||
if (view.collection === "dependencies") {
|
||||
const paths = view.paths === undefined ? undefined : new Set(view.paths);
|
||||
const selected = selectViewRows(
|
||||
info?.module.dependencies ?? [],
|
||||
info.module.dependencies,
|
||||
view,
|
||||
(row, index) =>
|
||||
(indices?.has(index) ?? true) && (paths?.has(row.path) ?? true),
|
||||
@@ -353,7 +362,7 @@ export const projectGoBinaryView = (
|
||||
}
|
||||
const keys = view.keys === undefined ? undefined : new Set(view.keys);
|
||||
const selected = selectViewRows(
|
||||
info?.module.settings ?? [],
|
||||
info.module.settings,
|
||||
view,
|
||||
(row, index) =>
|
||||
(indices?.has(index) ?? true) && (keys?.has(row.key) ?? true),
|
||||
|
||||
@@ -217,7 +217,7 @@ export const projectModelArtifactView = (
|
||||
parent_digest: parent.evidenceId.slice(3),
|
||||
artifact: { path: report.artifact.path, sha256: report.artifact.sha256 },
|
||||
limitations: boundedViewMessages(
|
||||
parent.limitations,
|
||||
[...parent.limitations, ...report.limitations],
|
||||
parent.evidenceId,
|
||||
"limitations",
|
||||
),
|
||||
|
||||
@@ -206,3 +206,40 @@ it("pages matching original indices and omits dependency vectors unless selected
|
||||
next_offset: null,
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["packages", "edges"] as const)(
|
||||
"distinguishes unavailable Rust %s from decoded empty records",
|
||||
(collection) => {
|
||||
for (const available of [false, true]) {
|
||||
const metadata = report.auditable_metadata!;
|
||||
const result = projectAnalysisView(
|
||||
parent({
|
||||
...report,
|
||||
auditable_metadata: available
|
||||
? {
|
||||
...metadata,
|
||||
packages: [],
|
||||
root_package_index: null,
|
||||
dependency_edges: [],
|
||||
cyclic_dependency_indices: [],
|
||||
}
|
||||
: null,
|
||||
}),
|
||||
{ kind: "rust", collection, offset: 3, limit: 1 },
|
||||
);
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.coverage).toEqual({
|
||||
status: available ? "empty" : "unavailable",
|
||||
examined: 0,
|
||||
total: available ? 0 : null,
|
||||
next_offset: null,
|
||||
exhausted: available,
|
||||
});
|
||||
expect(result.value).toMatchObject({
|
||||
source_coverage: {
|
||||
embedded_records: available ? "complete" : "unknown",
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
boundedViewMessages,
|
||||
completeWithinViewCoverage,
|
||||
pageViewCoverage,
|
||||
unavailableViewCoverage,
|
||||
} from "./analysisViewCoverage.js";
|
||||
import { analysisViewInputError } from "./analysisViewErrors.js";
|
||||
import { selectViewRows } from "./selectViewRows.js";
|
||||
@@ -190,6 +191,15 @@ export const projectRustBinaryView = (
|
||||
summary,
|
||||
coverage: completeWithinViewCoverage(1, 1),
|
||||
});
|
||||
if (metadata === null)
|
||||
return ok({
|
||||
kind: "rust" as const,
|
||||
view,
|
||||
...shared,
|
||||
rust: summary,
|
||||
page: { collection: view.collection, rows: [] },
|
||||
coverage: unavailableViewCoverage(),
|
||||
});
|
||||
const indices =
|
||||
view.indices === undefined ? undefined : new Set(view.indices);
|
||||
const fields =
|
||||
@@ -199,7 +209,7 @@ export const projectRustBinaryView = (
|
||||
const versions =
|
||||
view.versions === undefined ? undefined : new Set(view.versions);
|
||||
const selected = selectViewRows(
|
||||
metadata?.packages ?? [],
|
||||
metadata.packages,
|
||||
view,
|
||||
(row, index) =>
|
||||
(indices?.has(index) ?? true) &&
|
||||
@@ -238,7 +248,7 @@ export const projectRustBinaryView = (
|
||||
const to =
|
||||
view.to_indices === undefined ? undefined : new Set(view.to_indices);
|
||||
const selected = selectViewRows(
|
||||
metadata?.dependency_edges ?? [],
|
||||
metadata.dependency_edges,
|
||||
view,
|
||||
(row, index) =>
|
||||
(indices?.has(index) ?? true) &&
|
||||
|
||||
@@ -247,26 +247,6 @@ it("preserves TShark canonical output for a caller-selected field alias", async
|
||||
expect(report.tool.arguments).toContain("bootp.type");
|
||||
});
|
||||
|
||||
it("refuses producer frames outside the selected input read scope before paging", async () => {
|
||||
const f = await fixture("many");
|
||||
const request = { path: f.path, fields: ["ip.addr"], match_limit: 1 };
|
||||
expect(
|
||||
await f.service.inspect({ ...request, packet_limit: 10 }),
|
||||
).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisOutputError", operation: "inspect_packet_capture" },
|
||||
});
|
||||
const admitted = await f.service.inspect({ ...request, packet_limit: 90 });
|
||||
if (!admitted.ok) throw admitted.error;
|
||||
const report = packetCaptureSchema.parse(admitted.value.normalized_result);
|
||||
expect(report.output_window).toEqual({
|
||||
matching_packets: 90,
|
||||
returned_packets: 1,
|
||||
has_more: true,
|
||||
});
|
||||
expect(report.packets[0]?.["frame.number"]).toEqual(["1"]);
|
||||
});
|
||||
|
||||
it("returns canonical alias output through the public packet-capture CLI", async () => {
|
||||
const f = await fixture("alias-canonical");
|
||||
const cli = Cli.create("capture-alias-test", { sync: false });
|
||||
|
||||
@@ -17,7 +17,7 @@ it("preserves repeated field values and missing fields while discarding export i
|
||||
},
|
||||
{ _source: { layers: { "frame.number": ["7"] } } },
|
||||
]);
|
||||
expect(parsePacketOutput(output, 7)).toEqual([
|
||||
expect(parsePacketOutput(output)).toEqual([
|
||||
{ "frame.number": ["2"], "ip.addr": ["192.0.2.1", "192.0.2.2"] },
|
||||
{ "frame.number": ["7"] },
|
||||
]);
|
||||
@@ -49,7 +49,7 @@ it.each([
|
||||
])("rejects malformed producer fields: $layers", ({ layers, issue }) => {
|
||||
let failure: unknown;
|
||||
try {
|
||||
parsePacketOutput(JSON.stringify([{ _source: { layers } }]), null);
|
||||
parsePacketOutput(JSON.stringify([{ _source: { layers } }]));
|
||||
} catch (error) {
|
||||
failure = error;
|
||||
}
|
||||
@@ -72,7 +72,6 @@ it.each([
|
||||
_source: { layers: { "frame.number": frame } },
|
||||
})),
|
||||
),
|
||||
null,
|
||||
);
|
||||
} catch (error) {
|
||||
failure = error;
|
||||
@@ -89,18 +88,7 @@ it("preserves canonical field names emitted for a selected alias", () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(parsePacketOutput(output, null)).toEqual([
|
||||
expect(parsePacketOutput(output)).toEqual([
|
||||
{ "frame.number": ["1"], "dhcp.type": ["1"] },
|
||||
]);
|
||||
});
|
||||
|
||||
it("validates every frame against the input read limit while allowing filtered gaps", () => {
|
||||
const output = JSON.stringify(
|
||||
[2, 7].map((frame) => ({
|
||||
_source: { layers: { "frame.number": [String(frame)] } },
|
||||
})),
|
||||
);
|
||||
expect(parsePacketOutput(output, 7)).toHaveLength(2);
|
||||
expect(() => parsePacketOutput(output, 6)).toThrow(AnalysisOutputError);
|
||||
expect(parsePacketOutput(output, null)).toHaveLength(2);
|
||||
});
|
||||
@@ -368,7 +368,7 @@ mcpTest(
|
||||
const page = goPage(parseEvidence(empty.structuredContent));
|
||||
expect(page).toMatchObject({
|
||||
page: { rows: [] },
|
||||
coverage: { total: 0 },
|
||||
coverage: { status: "unavailable", total: null, exhausted: false },
|
||||
source_coverage: {
|
||||
build_info_available: false,
|
||||
module_parse_complete: null,
|
||||
|
||||
Reference in new issue
Block a user