Files
rea/src/application/android/AndroidSearchEvidenceSummary.ts
T
akushonkamen 7a0140b4aa fix(android): bound inline search Evidence with retained inventory provenance (#1890)
Focused and zero-match search_android_classes responses embedded every
examined class name: JadxSession records each raw list_classes page into
observed calls, those calls ride into Evidence raw_result on the
registration path, and the complete inline Evidence is serialized into
every tool response (issue #1850: 382,435 bytes for one match).

When the server session retains Evidence, record the complete raw-call
record first, then deliver a summary Evidence that keeps the pagination
audit (totals, offsets, page sizes, elided item counts), the unchanged
normalized matches and truthful coverage metadata, and links the
retained complete inventory by Evidence ID. Sessions without retention
keep the complete inline delivery, so complete CLI JSON output is
unchanged and no provenance is discarded.

🤖 Generated with AI assistance (disclosure voluntary per repo policy)
2026-10-12 01:07:22 +08:00

121 lines
3.8 KiB
TypeScript

import type { EvidenceWriter } from "../investigation/InvestigationRecordPort.js";
import { createEvidence, type Evidence } from "../../domain/evidence.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import type { JsonValue } from "../../domain/jsonValue.js";
import { ok, type Result } from "../../domain/result.js";
/**
* Complete class-inventory pages dominate raw-call provenance for focused
* searches: one traversal embeds every examined class name regardless of the
* match count. The summary keeps the pagination audit inline (totals, page
* offsets and sizes, elided item counts) and links the retained record that
* still carries the complete inventory.
*/
const elideInventoryText = (text: string): string | undefined => {
let page: JsonValue;
try {
page = JSON.parse(text) as JsonValue;
} catch {
return undefined;
}
if (
typeof page !== "object" ||
page === null ||
Array.isArray(page) ||
!Array.isArray(page.items)
)
return undefined;
const { items, ...audited } = page;
return JSON.stringify({ ...audited, items_omitted: items.length });
};
/** Replace inventoried page bodies with their pagination audit. */
const summarizeInventoryCall = (call: JsonValue): JsonValue => {
if (
typeof call !== "object" ||
call === null ||
Array.isArray(call) ||
call.operation !== "list_classes"
)
return call;
const response = call.response;
if (
typeof response !== "object" ||
response === null ||
Array.isArray(response) ||
!Array.isArray(response.content)
)
return call;
let elided = false;
const content = response.content.map((entry: JsonValue) => {
if (
typeof entry !== "object" ||
entry === null ||
Array.isArray(entry) ||
typeof entry.text !== "string"
)
return entry;
const summarized = elideInventoryText(entry.text);
if (summarized === undefined) return entry;
elided = true;
return { ...entry, text: summarized };
});
return elided
? { ...call, inventory_elided: true, response: { ...response, content } }
: call;
};
/**
* Retain the complete search Evidence — full raw provider calls included —
* in the session record, then deliver a bounded summary Evidence inline.
* Callers without a retaining session keep the complete inline delivery, so
* complete CLI JSON output is unchanged. Coverage metadata stays truthful in
* both copies: the examined inventory is never rewritten to matched-only.
*/
export const summarizeRetainedAndroidSearch = (
evidence: Evidence,
recordEvidence: EvidenceWriter["recordEvidence"] | undefined,
): Result<Evidence, AnalysisError> => {
if (recordEvidence === undefined) return ok(evidence);
const recorded = recordEvidence(evidence);
if (!recorded.ok) return recorded;
const subject = evidence.subject;
const raw = evidence.raw_result;
const summarized: JsonValue =
raw === null ||
typeof raw !== "object" ||
Array.isArray(raw) ||
!Array.isArray(raw.calls)
? raw
: {
...raw,
complete_evidence_id: evidence.evidence_id,
calls: raw.calls.map(summarizeInventoryCall),
};
return ok(
createEvidence(
subject === null
? undefined
: {
path: subject.local_path,
sha256: subject.digest.sha256,
format: subject.format,
...(subject.architecture === null
? {}
: { architecture: subject.architecture }),
},
evidence.provider,
{
operation: evidence.operation,
parameters: evidence.parameters,
result: evidence.normalized_result,
rawResult: summarized,
confidence: evidence.confidence,
limitations: evidence.limitations,
locations: evidence.locations,
evidenceLinks: [evidence.evidence_id],
},
),
);
};