perf(entropy): separate physical reads from report resolution (#6950)

* perf(entropy): separate physical reads from report blocks

* test(entropy): type the injected positional short-read boundary

* test(entropy): retain full catalog transport capacity in task probe

* docs(perf): record real entropy task costs and limits

* docs(perf): record latest-main entropy qualification
This commit is contained in:
gensokyo-dev authored and GitHub committed 2026-10-24 13:16:08 +08:00
1 parent b869fa762a
commit b5c42dfc2a
7 files changed
+1973 -7

No files matched your search

@@ -0,0 +1,94 @@
"""Verify 18 completed entropy tasks and report whole-task resource/token costs."""
import argparse
from pathlib import Path
import hashlib, json, statistics
import tiktoken
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--receipts', type=Path, required=True)
parser.add_argument('--output', type=Path, required=True)
options = parser.parse_args()
b = options.receipts
cohort = b / 'cohort'
index = json.loads((cohort / 'index.json').read_text())
assert len(index) == 18 and all((r['exit'] == 0 for r in index))
assert len({item['name'] for item in index}) == 18
enc = tiktoken.get_encoding('cl100k_base')
compact = lambda d: json.dumps(d, ensure_ascii=False, separators=(',', ':'), allow_nan=False)
count = lambda s: len(enc.encode(s, disallowed_special=()))
digest = lambda p: hashlib.sha256(p.read_bytes()).hexdigest()
rows = []
for item in index:
p = cohort / item['name']
r = json.loads((p / 'report.json').read_text())
catalog = json.loads((p / 'catalog.json').read_text())
tx = json.loads((p / 'transcript.json').read_text())
scope = next((json.loads(l) for l in reversed((cohort / (item['name'] + '.log')).read_text().splitlines()) if l.startswith('{"resource_scope":')))
obs = scope['payload_resource_observation']
events = obs['resource_events']
assert r['oracle_sha256'] == digest(b / f"{item['case']}-oracle.json")
assert item['exit'] == 0 and r['status'] == 'passed' and r['complete_source_export_verified'] and (not r['cleanup_failures']) and (not r['receipt_failures'])
assert scope['abort'] is None and scope['payload_resource_observation_verified'] and (obs['payload_exit_code'] == 0) and obs['payload_cleanup_verified']
assert all((events['memory.events'][k] == 0 for k in ['high', 'max', 'oom', 'oom_kill'])) and events['pids.events']['max'] == 0
assert [e['name'] for e in tx] == ['inspect_byte_entropy', 'export_evidence_bundle']
assert all((not e['reply'].get('isError', False) for e in tx))
inp = count(compact([{k: t[k] for k in ['name', 'description', 'inputSchema']} for t in catalog['tools']]))
args = sum((count(compact({'name': e['name'], 'arguments': e['input']})) for e in tx))
replies = sum((count(c['text']) for e in tx for c in e['reply']['content'] if c['type'] == 'text'))
prompt = count(r['question'])
instructions = count(r['server_instructions'])
rows.append({
**item,
'catalog_tools': len(catalog['tools']),
'catalog_sha256': digest(p / 'catalog.json'),
'parent_evidence_id': r['parent_evidence_id'],
'source_report_sha256': r['source_report_sha256'],
'runtime_identity_sha256': r['runtime_identity_sha256'],
'wall_seconds': scope['elapsed_seconds'],
'cpu_seconds': obs['cpu_stat']['usage_usec'] / 1000000.0,
'kernel_family_peak_bytes': obs['memory_peak_bytes'],
'inspection_ms': r['calls'][0]['elapsed_ms'],
'discovery_ms': r['phases']['discovery_ms'],
'token_estimate': {
'question': prompt,
'instructions': instructions,
'directory_once': inp,
'arguments': args,
'text_replies': replies,
'whole_task': prompt + instructions + inp + args + replies,
'raw_catalog': count(compact(catalog)),
},
'receipt_sha256': {n: digest(p / n) for n in ['report.json', 'transcript.json', 'catalog.json', 'source.evidence.json']},
'payload_exit_events': events,
'payload_cleanup_verified': True,
'whole_scope_events_final': scope['resource_events_final'],
})
summary = {}
for case in ['llvm-small-block', 'llvm-large-block', 'ls']:
rs = [r for r in rows if r['case'] == case]
for k in ['catalog_sha256', 'source_report_sha256', 'parent_evidence_id']:
assert len({r[k] for r in rs}) == 1, (case, k)
summary[case] = {}
for lane in ['before', 'after']:
ls = [r for r in rs if r['lane'] == lane]
assert len(ls) == 3
assert {r['repeat'] for r in ls} == {1, 2, 3}
metrics = {k: [r[k] for r in ls] for k in ['wall_seconds', 'cpu_seconds', 'kernel_family_peak_bytes', 'inspection_ms', 'discovery_ms']}
metrics['whole_task_tokens'] = [r['token_estimate']['whole_task'] for r in ls]
summary[case][lane] = {k: {'median': statistics.median(v), 'min': min(v), 'max': max(v)} for k, v in metrics.items()}
identities = {lane: json.loads((b / f'{lane}-runtime-identity.json').read_text()) for lane in ['before', 'after']}
assert all(identity['source_clean'] for identity in identities.values())
for row in rows:
assert row['runtime_identity_sha256'] == digest(b / f"{row['lane']}-runtime-identity.json")
result = {
'tiktoken': tiktoken.__version__,
'encoding': enc.name,
'actual_model_usage': None,
'revisions': {l: i['source_revision'] for l, i in identities.items()},
'identity_receipts': {l: digest(b / f'{l}-runtime-identity.json') for l in identities},
'oracles': {c: {'sha256': digest(b / f'{c}-oracle.json'), 'artifact': json.loads((b / f'{c}-oracle.json').read_text())['expected']['artifact']} for c in summary},
'summary': summary,
'recordings': rows,
'measurement': 'Three alternating fresh production stdio SDK sessions per lane/case; exact discovered toolDefinition/default validator, fresh HOME, Node compile cache disabled, OS caches unflushed. Each task discovers complete catalog, inspects bytes and exports/compares full source Evidence; all oracle facts checked. Whole process-family CPU and kernel peak include client/server, runtime fingerprint verification, serialization, export and cleanup. Oracle generation and tokenization outside measured tasks. One-CPU quota, 3GiB early stop/4GiB hard/no swap; payload exit/cleanup/events verified, final whole-scope counters unavailable. Token serialization estimate includes question/instructions/all input name+description+schema once/all arguments/all text replies, raw catalog separately. Host framing, replay, model inference quality and billing unmeasured.',
}
options.output.write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps(summary, indent=2))
@@ -0,0 +1,56 @@
"""Independent Python histogram oracle; reads artifact bytes without execution."""
import argparse
from collections import Counter
import hashlib
import json
import math
from pathlib import Path
def entropy(counts, size):
if not size:
return 0
value = -sum((counts[k] / size) * math.log2(counts[k] / size) for k in sorted(counts) if counts[k])
return math.floor(value * 10000 + 0.5) / 10000
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--path', required=True)
parser.add_argument('--output', required=True)
parser.add_argument('--block-size', type=int, required=True)
parser.add_argument('--offset', type=int, default=0)
parser.add_argument('--length', type=int)
args = parser.parse_args()
path = Path(args.path)
before = path.stat()
counts = Counter()
digest = hashlib.sha256()
with path.open('rb') as source:
while chunk := source.read(1024 * 1024):
digest.update(chunk)
counts.update(chunk)
length = args.length if args.length is not None else before.st_size - args.offset
assert 0 <= args.offset <= before.st_size and 0 <= length <= before.st_size - args.offset
blocks, runs = [], []
source.seek(args.offset)
for offset in range(args.offset, args.offset + length, args.block_size):
chunk = source.read(min(args.block_size, args.offset + length - offset))
value = entropy(Counter(chunk), len(chunk))
block = {'offset': offset, 'length': len(chunk), 'entropy': value}
blocks.append(block)
classification = 'high' if value >= 7.2 else 'low' if value <= 1 else None
if classification is not None:
if runs and runs[-1]['classification'] == classification and runs[-1]['offset'] + runs[-1]['length'] == offset:
runs[-1]['length'] += len(chunk)
else:
runs.append({'offset': offset, 'length': len(chunk), 'classification': classification})
after = path.stat()
assert (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns, before.st_ctime_ns) == (after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns, after.st_ctime_ns)
expected = {'artifact': {'path': args.path, 'bytes': before.st_size, 'sha256': digest.hexdigest()}, 'block_size':args.block_size,'window':{'offset':args.offset,'length':length},'file_entropy':entropy(counts,before.st_size),'thresholds':{'high':7.2,'low':1},'blocks':blocks,'runs':runs}
request = {'path':args.path,'block_size':args.block_size,'offset':args.offset}
if args.length is not None:
request['length'] = args.length
Path(args.output).write_text(json.dumps({'input':request,'expected':expected},indent=2)+'\n')
print(json.dumps({'path':args.path,'sha256':digest.hexdigest(),'blocks':len(blocks),'runs':len(runs)}))
if __name__ == '__main__':
main()
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,158 @@
# Byte entropy: separate physical reads from report resolution
Issue: [#6947](https://github.com/morluto/rea/issues/6947).
The question is to return the full-file SHA-256 and entropy, every block in a
selected byte window, and adjacent high/low runs, preserving complete source
Evidence. `ByteEntropyService` previously used `block_size` for both reporting
and physical reads, including outside the selected window. Small report blocks
therefore caused hundreds of thousands of asynchronous reads of a large file.
The shared service now fills a bounded 64 KiB buffer before calculating the
caller-selected blocks. Supported power-of-two block sizes divide that buffer;
the window starts its own block sequence, and only its final block can be short.
Short physical reads are joined before interpretation. The same stable-file,
whole-file hash, cancellation, capacity and descriptor-cleanup owners remain.
No new cache, parser, contract or output selection is introduced.
## Fixed comparison
Baseline: `1d896e03b776453282beca7fd89338be778bfca2`.
Measured implementation: `3ed3276d0ee79c291e1006901ace5b06626c1d4e`.
Both were clean and built with Node 24.22.1, npm 11.16.0 and SDK 2.3.1.
The runtime receipt fingerprints 2,582 compiled/catalog/entrypoint/dependency
and driver files in each lane, checked before and after every task.
Real artifacts are installed Linux x64 binaries, inspected without execution:
| Artifact | Bytes | SHA-256 |
| ----------------------------------------------------- | ----------: | ------------------------------------------------------------------ |
| LLVM 16 (`/usr/lib/x86_64-linux-gnu/libLLVM-16.so.1`) | 123,379,936 | `f62d254b7f2bf42df8c8b07d46ee3bb4c2cafeca436b2e6bc6ccbe4581f58f40` |
| GNU ls (`/usr/bin/ls`) | 151,344 | `cb30d69b24245bf2ecdc9e7f53bbad19159999970b6d82c0c00c7d32d9e37aa4` |
`byte-entropy-oracle.py` independently computes Python byte histograms,
Shannon entropy, selected blocks, rounded-value run classifications and digests.
It checks file state around its reads. Every returned fact agrees with this
oracle, and both lanes have identical complete report hashes and Evidence IDs.
There are three alternating fresh production stdio MCP sessions per lane/case,
18 tasks total. Each discovers all 222 tools, uses exact discovered
`toolDefinition` validation, inspects the bytes, exports the complete Evidence
bundle and verifies its parent record. Fresh HOME/client/server, Node compile
cache disabled, OS caches unflushed. These are fresh sessions; no warm-runtime
speedup is claimed. Cached-catalog SDK bulk validation was not measured here.
| Task (3 samples per lane) | Whole elapsed median, s | Family CPU median, s | Kernel family peak median, MiB | Whole-task estimated tokens |
| ----------------------------------------------------------- | ----------------------: | -------------------: | -----------------------------: | --------------------------: |
| LLVM: 256-byte blocks, offset 3, length 65,539 (257 blocks) | 29.842 → 8.584 | 25.286 → 6.358 | 629.5 → 672.4 | 62,000 → 62,000 |
| LLVM: whole file, 65,536-byte blocks (1,883 blocks) | 12.314 → 12.115 | 7.581 → 7.325 | 667.9 → 674.3 | 91,900 → 91,900 |
| ls: whole file, 256-byte blocks (592 blocks) | 7.075 → 8.286 | 5.804 → 5.528 | 659.1 → 659.3 | 67,215 → 67,215 |
The large fine-resolution task improves elapsed median by 71.2% and CPU by
74.9%. Elapsed ranges are 28.541–33.290 → 7.981–10.396 seconds; CPU ranges
23.600–25.429 → 6.049–6.633 seconds. Its kernel peak increases by about
43 MiB (ranges 620.4–637.5 → 665.7–673.2 MiB). This is a speed/CPU improvement
with higher measured peak memory, not a memory optimization.
The other controls do not establish a stable whole-task speedup. The small
file's elapsed median is worse; ranges overlap at 6.469–9.892 → 6.169–9.188
seconds. No general latency or memory benefit is claimed. Full-file reading
and hashing remain necessary even when the selected window is short or empty.
A separate single real-file service probe delegated to actual `open/stat/read`
while counting reads: 481,955 → 1,885 physical reads of all 123,379,936 bytes,
maximum request 256 → 65,536 bytes, service elapsed 17.09 → 1.34 seconds.
The complete Evidence objects were equal and independently checked. This probe
locates the work removed; it is not the repeated public-task performance table.
The [machine-readable report](./byte-entropy-reads.json) preserves ranges,
per-sample phase timings, calls, receipt hashes, artifact identities and commands.
`cl100k_base` via tiktoken 0.14.0 counts the fixed question, server instructions,
all tool names/descriptions/input schemas once, every argument and every text
reply including export. Input-directory cost is 56,685 tokens; raw catalog with
output schemas is separately 838,375. Actual model billing, host framing/replay,
model reasoning quality and inference latency were not measured. No token saving
is claimed: replies and catalog remain unchanged.
## Resource and correctness verification
All tasks ran serially under a one-CPU quota, 3 GiB early memory stop, 4 GiB hard
limit, zero swap and 192-task ceiling. Whole process-family CPU and kernel memory
peak include client/server, runtime fingerprint reads, serialization, source
export and cleanup; kernel peak includes charged file cache. Independent oracle
generation and tokenization are outside timed tasks. Payload exits, remaining
owned processes and memory/PID-limit events were checked: all task payloads
exited 0, cleanup verified, no guard stop/OOM. Final whole-scope counters were
unavailable after transient-scope removal and are not represented as verified.
Executed additional checks:
- Six real CLI calls (three cases in both lanes): every oracle fact, complete
CLI Evidence equality and matching MCP parent identity passed.
- 36 tests in four exact source/public-boundary files passed, including short
reads, unaligned windows across buffers, partial tails at minimum/intermediate/
maximum block sizes, empty windows, cancellation through finalization,
identity changes, capacity refusal, retained summaries and cleanup retry.
- Six serial static tasks (typecheck, lint including module boundaries, format,
knip, metadata and required compilation), plus architecture guards passed.
Static family peak was 1.96 GiB; focused-test/preparation peak was 2.15 GiB.
- An initial typecheck failure in the new short-read test's injected callback
was fixed with an explicitly typed positional boundary. Its failed receipt is
retained, not counted as a successful run. Initial local formatter path and
early profile verification attempts also failed before their intended checks;
corrected checks are recorded separately.
The public experiments qualify static byte inspection on these Linux files.
They do not establish behavior on every filesystem or operating system and do
not execute LLVM or ls. CLI/MCP contracts, Evidence fields, input limits and
provider authority remain unchanged; generated catalogs and builds are ignored.
After integrating main `21a20306b`, the six serial static tasks, architecture
guards and all 36 focused cases passed again. Three fresh real MCP/source-export
tasks and three CLI calls matched every oracle fact and the frozen cohort's
report/parent identity. The integrated test/preparation family peaked at
2.23 GiB with verified payload exit/cleanup and no guard stop/OOM. These are
integration qualification runs; they do not replace the fixed performance pins.
The committed cost verifier also reproduced the complete measurement JSON.
## Reproduction
Build both pinned checkouts using their locked toolchain, serially. Supply an
independent installed dependency tree matching the lock, without upgrading host
software. Use the exact artifact digests above. The oracle accepts explicit
`--path`, `--block-size`, `--offset`, `--length` and `--output`; omit length for
the two whole-file controls. For example:
```sh
python3 experiments/analysis-efficiency/byte-entropy-oracle.py \
--path /usr/lib/x86_64-linux-gnu/libLLVM-16.so.1 \
--block-size 256 --offset 3 --length 65539 --output /absolute/oracle.json
python3 experiments/analysis-efficiency/resource-run.py --observe-payload-exit -- \
node experiments/analysis-efficiency/byte-entropy-reads.mjs \
--runtime /absolute/pinned-checkout --oracle /absolute/oracle.json \
--identity /absolute/runtime-identity.json --output /absolute/new-task-directory
```
Runtime identity JSON contains `source_revision`, `source_clean`,
`node_sha256` and `files: [{path, sha256}]` with absolute paths. Fingerprint the
entire compiled runtime and catalogs, scripts, lock/toolchain files, actual SDK
JavaScript dependencies and the driver/helpers. Both sessions must use the same
Node and dependency contents. Use the same one-CPU/memory policy for the harness;
the historical runs additionally pinned the payload to CPUs 0–1 and the initial
observer to CPU 15. Do not compare an unguarded default-concurrency run as an
equivalent resource measurement.
Retained raw receipts are on the investigation host at
`/tmp/rea-perf-byte-entropy-20261024/`. This includes `cohort/index.json`,
per-task catalog/transcript/report/source export/guard logs, runtime identities,
three oracle JSONs, CLI stdout/stderr, boundary profiles and verification logs.
The cost verifier takes that receipt directory and a separate output:
```sh
python3 experiments/analysis-efficiency/byte-entropy-costs.py \
--receipts /absolute/receipt-directory --output /absolute/costs.json
```
It requires all 18 successful verified samples, matching parent/report/catalog
identity across lanes, successful source exports and oracle/runtime receipts.
Failed or stopped attempts cannot enter the published cohort.
@@ -0,0 +1,154 @@
/** Complete entropy question through production stdio, with full source export. */
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { createWriteStream } from "node:fs";
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { finished } from "node:stream/promises";
import { parseArgs } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { createRetainedQueryCaller } from "./retained-query-calls.mjs";
import { finalizeRetainedQueryReceipt } from "./retained-query-finalization.mjs";
const { values } = parseArgs({
options: Object.fromEntries(
["runtime", "oracle", "identity", "output"].map((name) => [
name,
{ type: "string" },
]),
),
});
for (const value of Object.values(values)) assert.equal(value, resolve(value));
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
const oracleBytes = await readFile(values.oracle);
const oracle = JSON.parse(oracleBytes);
const identityBytes = await readFile(values.identity);
const identity = JSON.parse(identityBytes);
await mkdir(values.output, { mode: 0o700 });
const account = await mkdtemp(join(tmpdir(), "rea-entropy-task-"));
const client = new Client({ name: "entropy-complete-task", version: "1" });
const transport = new StdioClientTransport({
command: process.execPath,
args: [join(values.runtime, "scripts/rea.mjs"), "mcp"],
maxBufferSize: 10485760,
env: {
HOME: account,
TMPDIR: account,
PATH: process.env.PATH,
REA_LOG_LEVEL: "silent",
NODE_OPTIONS: "--max-old-space-size=2048",
NODE_DISABLE_COMPILE_CACHE: "1",
UV_THREADPOOL_SIZE: "2",
},
stderr: "pipe",
});
const serverLog = createWriteStream(join(values.output, "server.log"));
const serverLogFinished = finished(serverLog);
serverLogFinished.catch(() => {});
transport.stderr?.pipe(serverLog);
const report = {
status: "running",
question:
"Return the file SHA-256, file-wide entropy, every entropy block and adjacent high/low run in the requested byte window; preserve the complete source Evidence.",
request: oracle.input,
calls: [],
phases: {},
client:
"Production stdio SDK, exact discovered toolDefinition and default validator",
runtime_identity_sha256: sha256(identityBytes),
oracle_sha256: sha256(oracleBytes),
driver_sha256: sha256(await readFile(new URL(import.meta.url))),
};
const transcript = [];
let definitions;
const call = createRetainedQueryCaller(
{
callTool(params, options) {
return client.callTool(params, {
...options,
toolDefinition: definitions.get(params.name),
});
},
},
(receipt, event) => {
report.calls.push(receipt);
transcript.push(event);
},
);
const observe = async (name, args, phase) => {
const result = await call(name, args, phase);
assert.notEqual(result.isError, true, JSON.stringify(result));
assert.ok(result.structuredContent);
return result.structuredContent;
};
const verifyRuntime = async () => {
assert.equal(sha256(await readFile(values.oracle)), sha256(oracleBytes));
assert.equal(sha256(await readFile(values.identity)), sha256(identityBytes));
assert.equal(sha256(await readFile(process.execPath)), identity.node_sha256);
assert.equal(
sha256(await readFile(new URL(import.meta.url))),
report.driver_sha256,
);
for (const item of identity.files)
assert.equal(sha256(await readFile(item.path)), item.sha256, item.path);
};
try {
await verifyRuntime();
const start = performance.now();
await client.connect(transport);
report.server_instructions = client.getInstructions() ?? "";
const catalog = await client.listTools();
definitions = new Map(catalog.tools.map((tool) => [tool.name, tool]));
await writeFile(join(values.output, "catalog.json"), JSON.stringify(catalog));
report.phases.discovery_ms = performance.now() - start;
const parent = await observe("inspect_byte_entropy", oracle.input, "inspect");
for (const [key, value] of Object.entries(oracle.expected))
assert.deepEqual(parent.normalized_result[key], value, key);
report.parent_evidence_id = parent.evidence_id;
report.source_report_sha256 = sha256(
JSON.stringify(parent.normalized_result),
);
report.artifact = parent.normalized_result.artifact;
await observe(
"export_evidence_bundle",
{ path: join(values.output, "source.evidence.json") },
"export",
);
const bundle = JSON.parse(
await readFile(join(values.output, "source.evidence.json")),
);
assert.deepEqual(
bundle.records.find((item) => item.evidence_id === parent.evidence_id),
parent,
);
report.complete_source_export_verified = true;
report.status = "passed";
} catch (cause) {
report.status = "failed";
report.error = String(cause);
} finally {
try {
await verifyRuntime();
} catch (cause) {
report.status = "failed";
report.identity_failure = String(cause);
}
await finalizeRetainedQueryReceipt({
client,
transport,
account,
serverLog,
serverLogFinished,
report,
transcript,
output: values.output,
});
if (
report.status !== "passed" ||
report.cleanup_failures.length ||
report.receipt_failures.length
)
process.exitCode = 1;
}
@@ -38,6 +38,70 @@ it("keeps whole-file identity and entropy while selecting unaligned blocks and a
expect(result.value.confidence).toBe("observed");
});
it.each([256, 4096, 65536])(
"keeps %i-byte blocks relative to an unaligned window across short physical reads",
async (blockSize) => {
const root = await createTestTempDirectory("rea-entropy-short-reads-");
const path = join(root, "bytes");
const bytes = Buffer.from(
Array.from({ length: 3 * 65536 + 256 }, (_, i) => i % 256),
);
await writeFile(path, bytes);
let reads = 0;
const service = new ByteEntropyService({
stat,
open: async (...args) => {
const file = await open(...args);
const read = file.read.bind(file);
Object.defineProperty(file, "read", {
value: async (
buffer: Buffer,
offset: number,
length: number,
position: number,
) => {
reads += 1;
return read(buffer, offset, Math.min(length, 3571), position);
},
});
return file;
},
});
try {
const result = await service.inspect({
path,
offset: 3,
length: 2 * 65536 + 1,
block_size: blockSize,
});
if (!result.ok) throw result.error;
const report = byteEntropySchema.parse(result.value.normalized_result);
const fullBlocks = (2 * 65536) / blockSize;
expect(report.blocks).toEqual([
...Array.from({ length: fullBlocks }, (_, i) => ({
offset: 3 + i * blockSize,
length: blockSize,
entropy: 8,
})),
{ offset: 3 + 2 * 65536, length: 1, entropy: 0 },
]);
expect(report.runs).toEqual([
{ offset: 3, length: 2 * 65536, classification: "high" },
{ offset: 3 + 2 * 65536, length: 1, classification: "low" },
]);
expect(report.file_entropy).toBe(8);
expect(report.artifact.sha256).toBe(
createHash("sha256").update(bytes).digest("hex"),
);
// Even with 3,571-byte short reads, finer report resolution does not
// require hundreds of asynchronous reads of this 193 KiB file.
expect(reads).toBeLessThan(100);
} finally {
await service.close();
}
},
);
it("rejects missing files, non-files and windows beyond EOF, but accepts empty windows", async () => {
const root = await createTestTempDirectory("rea-entropy-input-");
const path = join(root, "empty");
@@ -241,7 +241,10 @@ export class ByteEntropyService {
);
const end = input.offset + length;
const blocks: ByteEntropy["blocks"] = [];
const buffer = Buffer.alloc(input.block_size);
// Physical reads must not shrink with report resolution, including
// outside the selected window. Every admitted power-of-two block
// size divides this buffer, so only the window's final block is short.
const buffer = Buffer.alloc(65_536);
const counts = new Float64Array(256);
const hash = createHash("sha256");
let position = 0;
@@ -250,7 +253,7 @@ export class ByteEntropyService {
const selected = position >= input.offset && position < end;
const boundary =
position < input.offset ? input.offset : selected ? end : size;
const wanted = Math.min(input.block_size, boundary - position);
const wanted = Math.min(buffer.length, boundary - position);
let filled = 0;
while (filled < wanted) {
assertArtifactReadActive(options?.signal);
@@ -267,11 +270,18 @@ export class ByteEntropyService {
hash.update(bytes);
for (const byte of bytes) counts[byte] = (counts[byte] ?? 0) + 1;
if (selected)
blocks.push({
offset: position,
length: filled,
entropy: computeBlockEntropy(bytes),
});
for (
let offset = 0;
offset < filled;
offset += input.block_size
) {
const block = bytes.subarray(offset, offset + input.block_size);
blocks.push({
offset: position + offset,
length: block.length,
entropy: computeBlockEntropy(block),
});
}
position += filled;
}
if (