From e2463372eae6dcd0ec558d1e4668cfc3814c04a0 Mon Sep 17 00:00:00 2001 From: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Date: Sat, 24 Oct 2026 10:23:06 +0800 Subject: [PATCH] feat(python): query selected pyc code-object facts (#6928) * feat(python): query retained pyc code-object facts * test(python): bind complete pyc selection benchmark tasks * test(python): include server instructions in token accounting * docs(perf): record pyc selected-facts task measurements * test(python): keep oracle dependencies separate from selected targets * docs(perf): record guarded oracle requalification * test(python): narrow complete Evidence through its owning contract --- docs/python-pyc.md | 54 + .../analysis-efficiency/python-pyc-costs.py | 114 + .../analysis-efficiency/python-pyc-oracle.py | 103 + .../analysis-efficiency/python-pyc-views.json | 2265 +++++++++++++++++ .../analysis-efficiency/python-pyc-views.md | 59 + .../analysis-efficiency/python-pyc-views.mjs | 272 ++ src/cli/analysisEvidenceDelivery.ts | 52 +- src/cli/javascriptRouteDelivery.ts | 32 +- src/cli/pythonPycCommands.ts | 73 +- src/contracts/analysisViewToolContracts.ts | 2 +- .../python/pythonPycToolContracts.ts | 50 +- .../pythonPycDeliveryContracts.test.ts | 58 + src/domain/analysisView/analysisView.ts | 31 +- .../analysisView/analysisViewParent.test.ts | 52 + src/domain/analysisView/analysisViewParent.ts | 32 + src/domain/analysisView/nativeExportsView.ts | 33 +- src/domain/analysisView/pythonPycView.ts | 145 ++ src/server/createServer.ts | 1 + src/server/registerPythonPycTool.ts | 39 +- .../applications/pythonPycViews.test.ts | 257 ++ tests/acceptance/pythonPycCli.test.ts | 79 +- tests/boundary/mcp/pythonPycMcp.test.ts | 17 +- 22 files changed, 3664 insertions(+), 156 deletions(-) create mode 100644 experiments/analysis-efficiency/python-pyc-costs.py create mode 100644 experiments/analysis-efficiency/python-pyc-oracle.py create mode 100644 experiments/analysis-efficiency/python-pyc-views.json create mode 100644 experiments/analysis-efficiency/python-pyc-views.md create mode 100644 experiments/analysis-efficiency/python-pyc-views.mjs create mode 100644 src/contracts/pythonPycDeliveryContracts.test.ts create mode 100644 src/domain/analysisView/analysisViewParent.test.ts create mode 100644 src/domain/analysisView/pythonPycView.ts create mode 100644 tests/acceptance/applications/pythonPycViews.test.ts diff --git a/docs/python-pyc.md b/docs/python-pyc.md index 71567e9f9d..be09af8cd3 100644 --- a/docs/python-pyc.md +++ b/docs/python-pyc.md @@ -20,6 +20,60 @@ requires an absolute filesystem path and preserves that exact selection: } ``` +## Selected code objects + +Complete output remains the default. To read selected code objects from a large +report, request MCP `detail: "summary"`. The summary contains artifact/header +identity, source coverage and a limitation count; its +`normalized_result.parent_evidence_id` identifies the complete retained report. +Use that parent ID with `inspect_analysis_view` and: + +```json +{ + "kind": "python-pyc", + "collection": "code_objects", + "names": ["parse_args", "parse_known_args"], + "fields": [ + "name", + "qualname", + "filename", + "firstlineno", + "bytecode", + "nested_code" + ], + "offset": 0, + "limit": 64 +} +``` + +This is the request's `view` field. To receive these selected facts in the first +call, include the same `view` directly in `inspect_python_pyc` alongside +`detail: "summary"`. Later pages use the returned parent ID with +`inspect_analysis_view`. `view` requires summary delivery; complete delivery +rejects a view instead of ignoring it. `names` matches retained display strings and +returns every same-name object. `indices` selects original `code_objects` +indexes; combining names and indices intersects those selectors. Omitted +selectors include all rows, while `[]` selects none. Each row always includes +`source_index`, scoped to its parent Evidence; `fields: []` returns only that +index. Filtering preserves source order, and paging counts matching rows. +The view's `pyc.coverage` preserves source coverage independently of page +coverage. Views preserve `nested_code` indexes and their code-constant semantics. + +CLI summary delivery requires a durable destination for the complete Evidence: + +```bash +rea inspect-python-pyc module.pyc --detail summary --evidence_output module.evidence.json --json +``` + +Add `--view '{"kind":"python-pyc","collection":"code_objects","names":["parse_args"],"fields":["name","bytecode"]}'` +to select facts in that initial CLI call. + +The output's `summary` is derived Evidence. Pass its parent ID and the saved +bundle to `inspect-analysis-view --evidence-bundle module.evidence.json` for +offline queries. MCP views read retained Evidence, and offline views read the +bundle; neither requires the original pyc to remain present. Parse budgets and +complete-source retention still apply to summary delivery. + ## Observations The inline Evidence result contains the artifact path, SHA-256, and byte length. diff --git a/experiments/analysis-efficiency/python-pyc-costs.py b/experiments/analysis-efficiency/python-pyc-costs.py new file mode 100644 index 0000000000..d7dda56d7f --- /dev/null +++ b/experiments/analysis-efficiency/python-pyc-costs.py @@ -0,0 +1,114 @@ +"""Verify completed PYC selection/export tasks and count a declared token projection.""" +import argparse +import hashlib +import json +from pathlib import Path +import statistics + +import tiktoken + + +def compact(value): + return json.dumps(value, ensure_ascii=False, separators=(',', ':'), allow_nan=False) + + +def digest(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def inspect(path, encoding): + report = json.loads((path / 'report.json').read_text()) + catalog = json.loads((path / 'catalog.json').read_text()) + transcript = json.loads((path / 'transcript.json').read_text()) + scope = next(json.loads(line) for line in reversed(path.with_suffix('.log').read_text().splitlines()) + if line.startswith('{"resource_scope":')) + observed = scope.get('payload_resource_observation') or {} + events = observed.get('resource_events', {}) + assert report['status'] == 'passed' and report['runtime_identity_sha256'] + assert report['complete_source_export_verified'] is True + assert not report['cleanup_failures'] and not report['receipt_failures'] + assert scope['abort'] is None and scope['payload_resource_observation_verified'] is True + assert observed['payload_exit_code'] == 0 and observed['payload_cleanup_verified'] is True + assert all(events.get('memory.events', {}).get(key) == 0 for key in ['high', 'max', 'oom', 'oom_kill']) + assert events.get('pids.events', {}).get('max') == 0 + assert len(catalog['tools']) == report['catalog_tools'] == 221 + assert [event['name'] for event in transcript] == ['inspect_python_pyc', 'export_evidence_bundle'] + assert all('reply' in event and not event['reply'].get('isError', False) for event in transcript) + count = lambda text: len(encoding.encode(text, disallowed_special=())) + directory = [{key: tool[key] for key in ['name', 'description', 'inputSchema']} for tool in catalog['tools']] + request = transcript[0]['input'] + # Both lanes answer the same question; selected arguments disclose the exact fields/names. + question = (f"For {report['artifact']['path']}, return every matching code object for " + f"{', '.join(report['selected_names'])}, including its original source_index, name, qualname, " + "filename, firstlineno, bytecode bytes/SHA-256 and nested_code constant-reference indices. " + "Keep same-name alternatives, preserve source coverage and export complete source Evidence. " + "Do not execute the bytecode or infer runtime calls.") + assert ('detail' in request) == (report['lane'] == 'selected') + arguments = sum(count(compact({'name': event['name'], 'arguments': event['input']})) for event in transcript) + replies = sum(count(block['text']) for event in transcript for block in event['reply']['content'] if block['type'] == 'text') + inputs, prompt = count(compact(directory)), count(question) + instructions = count(report['server_instructions']) + return dict(case=path.name, lane=report['lane'], artifact=report['artifact'], + answer_count=report['answer_count'], answer_sha256=report['answer_sha256'], + parent_evidence_id=report['parent_evidence_id'], source_report_sha256=report['source_report_sha256'], + runtime_identity_sha256=report['runtime_identity_sha256'], catalog_sha256=digest(path / 'catalog.json'), + calls=report['calls'], phases=report['phases'], wall_seconds=scope['elapsed_seconds'], + cpu_seconds=observed['cpu_stat']['usage_usec'] / 1e6, + kernel_family_peak_bytes=observed['memory_peak_bytes'], + events_at_payload_exit=events, payload_cleanup_verified=True, + final_whole_scope_events=scope.get('resource_events_final'), + token_proxy=dict(question=prompt, server_instructions=instructions, input_catalog_once=inputs, arguments=arguments, + text_replies=replies, combined=prompt + instructions + inputs + arguments + replies, + complete_transport_catalog=count(compact(catalog))), + receipt_sha256={file.name: digest(file) for file in [path / 'report.json', path / 'catalog.json', + path / 'transcript.json', path / 'evidence.json', path.with_suffix('.log')]}) + + +def summarize(rows): + result = {} + for artifact in sorted({row['artifact']['sha256'] for row in rows}): + cases = [row for row in rows if row['artifact']['sha256'] == artifact] + for field in ['answer_count', 'answer_sha256', 'parent_evidence_id', 'source_report_sha256']: + assert len({row[field] for row in cases}) == 1, field + result[artifact] = {} + for lane in ['complete', 'selected']: + samples = [row for row in cases if row['lane'] == lane] + assert len(samples) == 3 + for field in ['runtime_identity_sha256', 'catalog_sha256']: + assert len({row[field] for row in samples}) == 1, field + metrics = {key: [row[key] for row in samples] for key in ['wall_seconds', 'cpu_seconds', 'kernel_family_peak_bytes']} + metrics['projected_tokens'] = [row['token_proxy']['combined'] for row in samples] + metrics['mcp_reply_bytes'] = [sum(call['reply_bytes'] for call in row['calls']) for row in samples] + result[artifact][lane] = {key: dict(median=statistics.median(values), minimum=min(values), maximum=max(values)) + for key, values in metrics.items()} + return result + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--cohort', type=Path, required=True) + parser.add_argument('--output', type=Path, required=True) + args = parser.parse_args() + manifest = json.loads((args.cohort / 'manifest.json').read_text()) + index = json.loads((args.cohort / 'index.json').read_text()) + assert len(index) == 18 and all(case['exit'] == 0 for case in index) + for file, expected in manifest['driver_files'].items(): + assert digest(Path(file)) == expected, file + identities = {} + for lane, path in manifest['runtime_identities'].items(): + identity = json.loads(Path(path).read_text()) + assert identity['source_clean'] is True + identities[lane] = dict(sha256=digest(Path(path)), source_revision=identity['source_revision']) + encoding = tiktoken.get_encoding('cl100k_base') + rows = [inspect(args.cohort / case['name'], encoding) for case in index] + assert all(row['runtime_identity_sha256'] == identities[row['lane']]['sha256'] for row in rows) + receipt = dict(tiktoken=tiktoken.__version__, encoding=encoding.name, actual_model_usage=None, + cohort=manifest, commands=index, runtime_identities=identities, + projection='Scripted fresh SDK tasks, not model runs. One fixed question, server instructions and complete input directory (name/description/inputSchema), every argument and MCP text reply including export. Raw catalog including output schemas reported separately. No framing, host injection, history replay, inference quality or billable usage measured.', + resources='CPU quota100%, payload affinity0-1, initial observer15. Early stop3GiB, hard4GiB, swap0. Fresh HOME/client/server; Node compile cache disabled, OS caches unflushed. Whole process-family CPU and kernel memory peak include file cache, runtime identity checks, hashing, assertions, SDK/server serialization and cleanup. Oracle generation and tokenization outside timed tasks. Payload-exit counters verified; final whole-scope counters unavailable.', + summary=summarize(rows), recordings=rows) + args.output.write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n') + + +if __name__ == '__main__': + main() diff --git a/experiments/analysis-efficiency/python-pyc-oracle.py b/experiments/analysis-efficiency/python-pyc-oracle.py new file mode 100644 index 0000000000..bc503d0c59 --- /dev/null +++ b/experiments/analysis-efficiency/python-pyc-oracle.py @@ -0,0 +1,103 @@ +"""Independent CPython code-object facts; never execute target bytecode.""" +import getopt +import hashlib +import importlib.util +import json +import marshal +from pathlib import Path +import py_compile +import sys +import types + +FIELDS = ['name', 'qualname', 'filename', 'firstlineno', 'bytecode', 'nested_code'] +NAMES = ['parse_args', 'parse_known_args', '_parse_known_args'] + + +def describe(path, names): + raw = path.read_bytes() + assert raw[:4] == importlib.util.MAGIC_NUMBER, "Use the matching CPython producer for this oracle" + root = marshal.loads(raw[16:]) + assert isinstance(root, types.CodeType) + objects, indices, children = [], {}, {} + + def nested(value, visited): + identity = id(value) + if identity in visited: + return [] + visited.add(identity) + if isinstance(value, types.CodeType): + return [value] + if isinstance(value, (tuple, list, frozenset, set)): + return [child for item in value for child in nested(item, visited)] + if isinstance(value, dict): + return [child for item in value.items() for child in nested(item, visited)] + return [] + + def visit(code): + if id(code) in indices: + return + indices[id(code)] = len(objects) + objects.append(code) + children[id(code)] = nested(code.co_consts, set()) + for child in children[id(code)]: + visit(child) + + visit(root) + rows = [dict(source_index=index, name=code.co_name, + qualname=code.co_qualname, filename=code.co_filename, + firstlineno=code.co_firstlineno, + bytecode=dict(bytes=len(code.co_code), + sha256=hashlib.sha256(code.co_code).hexdigest()), + nested_code=[indices[id(child)] for child in children[id(code)]]) + for index, code in enumerate(objects)] + return dict(artifact=dict(path=str(path.resolve()), bytes=len(raw), + sha256=hashlib.sha256(raw).hexdigest()), + python=sys.version, names=names, fields=FIELDS, rows=rows, + answer=[row for row in rows if row['name'] in names], + bytecode_execution=False) + + +# Keep the argument parser independent of the argparse/typing targets. +options, trailing = getopt.getopt(sys.argv[1:], '', + ['path=', 'compile-control=', 'output=', 'name=']) +if trailing: + raise SystemExit('Unexpected positional arguments; repeat --name NAME for selectors') +values, selected_names = {}, [] +for key, value in options: + if key == '--name': + selected_names.append(value) + elif key in values: + raise SystemExit('Duplicate option: ' + key) + else: + values[key] = value +if '--output' not in values: + raise SystemExit('--output is required') +path = Path(values['--path']) if '--path' in values else None +compile_control = Path(values['--compile-control']) if '--compile-control' in values else None +output = Path(values['--output']) +names = selected_names or NAMES +assert all(module not in sys.modules for module in ['argparse', 'typing']), 'Oracle dependency loaded a target module' +assert sys.implementation.name == 'cpython' and sys.version_info >= (3, 11) +if compile_control: + directory = compile_control + directory.mkdir(parents=True, exist_ok=False) + source = directory / 'control.py' + source.write_text('''class First: + def parse_args(self): + def inner(): + return "a" + "😀" * 40 + return inner + def parse_known_args(self): + return 1 +class Second: + def parse_args(self): + return 2 +def _parse_known_args(): + return 3 +''', encoding='utf8') + path = directory / 'control.pyc' + py_compile.compile(str(source), cfile=str(path), dfile='control.py', + doraise=True, invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH) +assert path is not None +output.write_text(json.dumps(describe(path, names), indent=2) + '\n') +assert all(module not in sys.modules for module in ['argparse', 'typing']), 'Oracle loaded a target module' diff --git a/experiments/analysis-efficiency/python-pyc-views.json b/experiments/analysis-efficiency/python-pyc-views.json new file mode 100644 index 0000000000..5d6048f51b --- /dev/null +++ b/experiments/analysis-efficiency/python-pyc-views.json @@ -0,0 +1,2265 @@ +{ + "tiktoken": "0.14.0", + "encoding": "cl100k_base", + "actual_model_usage": null, + "cohort": { + "host": { + "system": "Linux", + "node": "instance-20260526-guiflow", + "release": "6.1.0-54-cloud-amd64", + "version": "#1 SMP PREEMPT_DYNAMIC Debian 6.1.190-1 (2026-10-16)", + "machine": "x86_64", + "processor": "" + }, + "driver_source_revision": "29ff0893e3a5a9ea9bc52f9f85a653388f6bc48a", + "driver_files": { + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs": "b97a1f6b6cc671769468f48e20eb252d892a4bff6a5e4dd10aaa4867eb30faf8", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-oracle.py": "48fdd1d202fe66b7c1bd522738d9e5670977a3c35483668eb808456fb8c7cd4c", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-costs.py": "0174989d8a50fc394ce5fd14d143bb15f5ec1a465df11b329b155052349d5391", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/retained-query-calls.mjs": "6cef1ceda424622d6a9654e388ff22d246eeb6f9d7b182b2122a83a2378fdaf7", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/retained-query-finalization.mjs": "901092ae6dc7fda1a8869d62362a0a9f560e03d5ef5667a5c6ed660db000b9a2", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py": "7a9642ab86ba34290564f636eb080cacf38b4185a254028680b238bff172993b", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + }, + "runtime_identities": { + "complete": "/tmp/rea-perf-pyc-views-20261023/final-complete-runtime-identity.json", + "selected": "/tmp/rea-perf-pyc-views-20261023/final-selected-runtime-identity.json" + }, + "toolchain_wrapper_sha256": "8f7677ee09e15468c7379a5fb7333316111e6593194817c13e90ef7569ac9b27", + "node_version": "v24.22.1", + "resource_policy": { + "cpu_quota_percent": 100, + "payload_affinity": "0-1", + "observer_initial_affinity": "15", + "early_stop_bytes": 3221225472, + "hard_bytes": 4294967296, + "swap": 0 + }, + "oracle_files": { + "argparse": "327617746cbac0b86af6ebe9744454e754f3e51e758817e26505de23b462a2d3", + "typing": "abcaf5682b1bd70dad881050780cee0138dcf2a17121782a685bcb74bc3b0dab", + "control": "2df24a4b8b01222b468fc20a74dc2d734d5dd4ec2fdbdd521b1a5eca98ec6679" + }, + "sources": "No ambient environment snapshot persisted." + }, + "commands": [ + { + "name": "argparse-1-complete", + "lane": "complete", + "pair": 1, + "case": "argparse", + "runtime": "/home/yann/yann/rea-perf-6922-main-control-20261024", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-6922-main-control-20261024", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-complete-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-argparse-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-1-complete", + "--lane", + "complete" + ], + "exit": 0 + }, + { + "name": "argparse-1-selected", + "lane": "selected", + "pair": 1, + "case": "argparse", + "runtime": "/home/yann/yann/rea-perf-pyc-views-20261023", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-pyc-views-20261023", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-selected-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-argparse-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-1-selected", + "--lane", + "selected" + ], + "exit": 0 + }, + { + "name": "typing-1-complete", + "lane": "complete", + "pair": 1, + "case": "typing", + "runtime": "/home/yann/yann/rea-perf-6922-main-control-20261024", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-6922-main-control-20261024", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-complete-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-typing-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-1-complete", + "--lane", + "complete" + ], + "exit": 0 + }, + { + "name": "typing-1-selected", + "lane": "selected", + "pair": 1, + "case": "typing", + "runtime": "/home/yann/yann/rea-perf-pyc-views-20261023", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-pyc-views-20261023", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-selected-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-typing-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-1-selected", + "--lane", + "selected" + ], + "exit": 0 + }, + { + "name": "control-1-complete", + "lane": "complete", + "pair": 1, + "case": "control", + "runtime": "/home/yann/yann/rea-perf-6922-main-control-20261024", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-6922-main-control-20261024", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-complete-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-control-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-1-complete", + "--lane", + "complete" + ], + "exit": 0 + }, + { + "name": "control-1-selected", + "lane": "selected", + "pair": 1, + "case": "control", + "runtime": "/home/yann/yann/rea-perf-pyc-views-20261023", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-pyc-views-20261023", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-selected-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-control-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-1-selected", + "--lane", + "selected" + ], + "exit": 0 + }, + { + "name": "argparse-2-selected", + "lane": "selected", + "pair": 2, + "case": "argparse", + "runtime": "/home/yann/yann/rea-perf-pyc-views-20261023", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-pyc-views-20261023", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-selected-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-argparse-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-2-selected", + "--lane", + "selected" + ], + "exit": 0 + }, + { + "name": "argparse-2-complete", + "lane": "complete", + "pair": 2, + "case": "argparse", + "runtime": "/home/yann/yann/rea-perf-6922-main-control-20261024", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-6922-main-control-20261024", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-complete-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-argparse-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-2-complete", + "--lane", + "complete" + ], + "exit": 0 + }, + { + "name": "typing-2-selected", + "lane": "selected", + "pair": 2, + "case": "typing", + "runtime": "/home/yann/yann/rea-perf-pyc-views-20261023", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-pyc-views-20261023", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-selected-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-typing-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-2-selected", + "--lane", + "selected" + ], + "exit": 0 + }, + { + "name": "typing-2-complete", + "lane": "complete", + "pair": 2, + "case": "typing", + "runtime": "/home/yann/yann/rea-perf-6922-main-control-20261024", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-6922-main-control-20261024", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-complete-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-typing-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-2-complete", + "--lane", + "complete" + ], + "exit": 0 + }, + { + "name": "control-2-selected", + "lane": "selected", + "pair": 2, + "case": "control", + "runtime": "/home/yann/yann/rea-perf-pyc-views-20261023", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-pyc-views-20261023", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-selected-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-control-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-2-selected", + "--lane", + "selected" + ], + "exit": 0 + }, + { + "name": "control-2-complete", + "lane": "complete", + "pair": 2, + "case": "control", + "runtime": "/home/yann/yann/rea-perf-6922-main-control-20261024", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-6922-main-control-20261024", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-complete-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-control-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-2-complete", + "--lane", + "complete" + ], + "exit": 0 + }, + { + "name": "argparse-3-complete", + "lane": "complete", + "pair": 3, + "case": "argparse", + "runtime": "/home/yann/yann/rea-perf-6922-main-control-20261024", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-6922-main-control-20261024", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-complete-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-argparse-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-3-complete", + "--lane", + "complete" + ], + "exit": 0 + }, + { + "name": "argparse-3-selected", + "lane": "selected", + "pair": 3, + "case": "argparse", + "runtime": "/home/yann/yann/rea-perf-pyc-views-20261023", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-pyc-views-20261023", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-selected-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-argparse-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-3-selected", + "--lane", + "selected" + ], + "exit": 0 + }, + { + "name": "typing-3-complete", + "lane": "complete", + "pair": 3, + "case": "typing", + "runtime": "/home/yann/yann/rea-perf-6922-main-control-20261024", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-6922-main-control-20261024", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-complete-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-typing-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-3-complete", + "--lane", + "complete" + ], + "exit": 0 + }, + { + "name": "typing-3-selected", + "lane": "selected", + "pair": 3, + "case": "typing", + "runtime": "/home/yann/yann/rea-perf-pyc-views-20261023", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-pyc-views-20261023", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-selected-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-typing-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-3-selected", + "--lane", + "selected" + ], + "exit": 0 + }, + { + "name": "control-3-complete", + "lane": "complete", + "pair": 3, + "case": "control", + "runtime": "/home/yann/yann/rea-perf-6922-main-control-20261024", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-6922-main-control-20261024", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-complete-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-control-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-3-complete", + "--lane", + "complete" + ], + "exit": 0 + }, + { + "name": "control-3-selected", + "lane": "selected", + "pair": 3, + "case": "control", + "runtime": "/home/yann/yann/rea-perf-pyc-views-20261023", + "command": [ + "python3", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/resource-run.py", + "--observe-payload-exit", + "--", + "/tmp/rea-perf-native-exports-20261023/run-task.sh", + "env", + "NODE_DISABLE_COMPILE_CACHE=1", + "node", + "/home/yann/yann/rea-perf-pyc-views-20261023/experiments/analysis-efficiency/python-pyc-views.mjs", + "--runtime", + "/home/yann/yann/rea-perf-pyc-views-20261023", + "--identity", + "/tmp/rea-perf-pyc-views-20261023/final-selected-runtime-identity.json", + "--oracle", + "/tmp/rea-perf-pyc-views-20261023/final-control-oracle.json", + "--output", + "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-3-selected", + "--lane", + "selected" + ], + "exit": 0 + } + ], + "runtime_identities": { + "complete": { + "sha256": "34d76c255cd82c1d261e8c53c222e7207616258a4d19796ea7545a714865b3d6", + "source_revision": "6b3eee1ccf4c51f23bef7a0007c7b8e892f662db" + }, + "selected": { + "sha256": "b3d2f7478b0b4aec9d60e8bb3ba0043c5b06a1da5da4a62c2392fac9f65f0780", + "source_revision": "29ff0893e3a5a9ea9bc52f9f85a653388f6bc48a" + } + }, + "projection": "Scripted fresh SDK tasks, not model runs. One fixed question, server instructions and complete input directory (name/description/inputSchema), every argument and MCP text reply including export. Raw catalog including output schemas reported separately. No framing, host injection, history replay, inference quality or billable usage measured.", + "resources": "CPU quota100%, payload affinity0-1, initial observer15. Early stop3GiB, hard4GiB, swap0. Fresh HOME/client/server; Node compile cache disabled, OS caches unflushed. Whole process-family CPU and kernel memory peak include file cache, runtime identity checks, hashing, assertions, SDK/server serialization and cleanup. Oracle generation and tokenization outside timed tasks. Payload-exit counters verified; final whole-scope counters unavailable.", + "summary": { + "3dbccc61cbba13f44ad56489555eb92363eeedd92dd2dccaff7fc31088a5e136": { + "complete": { + "wall_seconds": { + "median": 5.358, + "minimum": 5.258, + "maximum": 5.562 + }, + "cpu_seconds": { + "median": 5.180368, + "minimum": 5.040796, + "maximum": 5.365922 + }, + "kernel_family_peak_bytes": { + "median": 676126720, + "minimum": 674852864, + "maximum": 694022144 + }, + "projected_tokens": { + "median": 57719, + "minimum": 57719, + "maximum": 57719 + }, + "mcp_reply_bytes": { + "median": 14891, + "minimum": 14891, + "maximum": 14891 + } + }, + "selected": { + "wall_seconds": { + "median": 5.459, + "minimum": 5.254, + "maximum": 5.558 + }, + "cpu_seconds": { + "median": 5.246611, + "minimum": 4.98266, + "maximum": 5.344233 + }, + "kernel_family_peak_bytes": { + "median": 686104576, + "minimum": 659181568, + "maximum": 691466240 + }, + "projected_tokens": { + "median": 57332, + "minimum": 57332, + "maximum": 57332 + }, + "mcp_reply_bytes": { + "median": 9083, + "minimum": 9083, + "maximum": 9083 + } + } + }, + "6fa55dc31dd55ddb1766369755b5cda156a42cf41193a0ff1de0189bffb4aa51": { + "complete": { + "wall_seconds": { + "median": 5.76, + "minimum": 5.46, + "maximum": 5.761 + }, + "cpu_seconds": { + "median": 5.531869, + "minimum": 5.300909, + "maximum": 5.577596 + }, + "kernel_family_peak_bytes": { + "median": 680681472, + "minimum": 670048256, + "maximum": 681426944 + }, + "projected_tokens": { + "median": 104415, + "minimum": 104415, + "maximum": 104415 + }, + "mcp_reply_bytes": { + "median": 338446, + "minimum": 338446, + "maximum": 338446 + } + }, + "selected": { + "wall_seconds": { + "median": 5.559, + "minimum": 5.46, + "maximum": 5.562 + }, + "cpu_seconds": { + "median": 5.368671, + "minimum": 5.21354, + "maximum": 5.378098 + }, + "kernel_family_peak_bytes": { + "median": 695984128, + "minimum": 663261184, + "maximum": 712794112 + }, + "projected_tokens": { + "median": 57300, + "minimum": 57300, + "maximum": 57300 + }, + "mcp_reply_bytes": { + "median": 9011, + "minimum": 9011, + "maximum": 9011 + } + } + }, + "71f4d60de5be5a21d80b934a39dbc7c2ca541a36f8bb90a07f460c3ca2252f9a": { + "complete": { + "wall_seconds": { + "median": 5.761, + "minimum": 5.66, + "maximum": 5.963 + }, + "cpu_seconds": { + "median": 5.531688, + "minimum": 5.43948, + "maximum": 5.748996 + }, + "kernel_family_peak_bytes": { + "median": 718323712, + "minimum": 700669952, + "maximum": 723959808 + }, + "projected_tokens": { + "median": 128858, + "minimum": 128858, + "maximum": 128858 + }, + "mcp_reply_bytes": { + "median": 495172, + "minimum": 495172, + "maximum": 495172 + } + }, + "selected": { + "wall_seconds": { + "median": 5.761, + "minimum": 5.56, + "maximum": 6.466 + }, + "cpu_seconds": { + "median": 5.566434, + "minimum": 5.329382, + "maximum": 6.249371 + }, + "kernel_family_peak_bytes": { + "median": 673751040, + "minimum": 672649216, + "maximum": 682242048 + }, + "projected_tokens": { + "median": 57316, + "minimum": 57316, + "maximum": 57316 + }, + "mcp_reply_bytes": { + "median": 8799, + "minimum": 8799, + "maximum": 8799 + } + } + } + }, + "recordings": [ + { + "case": "argparse-1-complete", + "lane": "complete", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc", + "bytes": 113662, + "sha256": "6fa55dc31dd55ddb1766369755b5cda156a42cf41193a0ff1de0189bffb4aa51" + }, + "answer_count": 3, + "answer_sha256": "5a7282ec16a5d3e0ee1bfedf4a0c296939fd5af2efc36d758e028d7938f6a890", + "parent_evidence_id": "ev_11532cdd39935b8e7838d2f75d278672e4dbbe828b6daa2baf797d7fb5753064", + "source_report_sha256": "d62fc8622c35b44eb5e9caa41b272316542f53c16da92e870eff562d5924851f", + "runtime_identity_sha256": "34d76c255cd82c1d261e8c53c222e7207616258a4d19796ea7545a714865b3d6", + "catalog_sha256": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc" + }, + "phase": "inspect", + "elapsed_ms": 2523.248425, + "reply_bytes": 338090, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-1-complete/evidence.json" + }, + "phase": "export", + "elapsed_ms": 56.10965699999997, + "reply_bytes": 356, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 654.0123, + "discovery_ms": 942.9613169999999, + "runtime_validation_after_ms": 679.8503890000002 + }, + "wall_seconds": 5.46, + "cpu_seconds": 5.300909, + "kernel_family_peak_bytes": 670048256, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 54883, + "arguments": 69, + "text_replies": 48858, + "combined": 104415, + "complete_transport_catalog": 822529 + }, + "receipt_sha256": { + "report.json": "07d8e492b6c5f8aa2b3565c95426f829ffc9164fbd93f690b836455eb5789602", + "catalog.json": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "transcript.json": "06982c20362bdd44df71ad058b8b8a10067ac7d94e3f4f3fdfba314bd6bb6319", + "evidence.json": "6f0d4cc7722a4d4a7b37d7e7be9840a6ee800651e8c7f838b91aa8b48eaaa38b", + "argparse-1-complete.log": "a76bde2f3f5daa0ab897f16e2d174a26312f8744679a2f2a92b93e9390b93e79" + } + }, + { + "case": "argparse-1-selected", + "lane": "selected", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc", + "bytes": 113662, + "sha256": "6fa55dc31dd55ddb1766369755b5cda156a42cf41193a0ff1de0189bffb4aa51" + }, + "answer_count": 3, + "answer_sha256": "5a7282ec16a5d3e0ee1bfedf4a0c296939fd5af2efc36d758e028d7938f6a890", + "parent_evidence_id": "ev_11532cdd39935b8e7838d2f75d278672e4dbbe828b6daa2baf797d7fb5753064", + "source_report_sha256": "d62fc8622c35b44eb5e9caa41b272316542f53c16da92e870eff562d5924851f", + "runtime_identity_sha256": "b3d2f7478b0b4aec9d60e8bb3ba0043c5b06a1da5da4a62c2392fac9f65f0780", + "catalog_sha256": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc", + "detail": "summary", + "view": { + "kind": "python-pyc", + "collection": "code_objects", + "names": ["parse_args", "parse_known_args", "_parse_known_args"], + "fields": [ + "name", + "qualname", + "filename", + "firstlineno", + "bytecode", + "nested_code" + ], + "offset": 0, + "limit": 64 + } + }, + "phase": "inspect", + "elapsed_ms": 2556.667184, + "reply_bytes": 8655, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-1-selected/evidence.json" + }, + "phase": "export", + "elapsed_ms": 53.5491379999994, + "reply_bytes": 356, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 628.131981, + "discovery_ms": 953.468577, + "runtime_validation_after_ms": 606.1992319999999 + }, + "wall_seconds": 5.46, + "cpu_seconds": 5.21354, + "kernel_family_peak_bytes": 695984128, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 55283, + "arguments": 126, + "text_replies": 1286, + "combined": 57300, + "complete_transport_catalog": 826451 + }, + "receipt_sha256": { + "report.json": "9f8ff46bf57013e02345588150956c700981710f944192a7e600998d96ff4b93", + "catalog.json": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "transcript.json": "56e40a58bbd6240bbc8344a5ea62fe9d107b311765f384bbad2080470319fa64", + "evidence.json": "6d509a2a1ee08bf5317d49ed1fb26a3cdf275814ffff0a2d183354a3a0941fa9", + "argparse-1-selected.log": "d34a7a0ec971c03f0680b83d38289517b7f7c4c757ac4f98890dd760ab0c97c9" + } + }, + { + "case": "typing-1-complete", + "lane": "complete", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc", + "bytes": 157945, + "sha256": "71f4d60de5be5a21d80b934a39dbc7c2ca541a36f8bb90a07f460c3ca2252f9a" + }, + "answer_count": 3, + "answer_sha256": "cc420a17dea4d43875fd64540d5f6bdad80af50c1d9090aa4d7cbd0f61099e18", + "parent_evidence_id": "ev_43fe9bc10193f43880ceb402d1790f3b97475c5d5b5510d22efebb56782efe41", + "source_report_sha256": "a851919d19f63cd64c339bd0d523eb3f3c95fcc96f8db7768c8e71b2bed0dc16", + "runtime_identity_sha256": "34d76c255cd82c1d261e8c53c222e7207616258a4d19796ea7545a714865b3d6", + "catalog_sha256": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc" + }, + "phase": "inspect", + "elapsed_ms": 2793.198855, + "reply_bytes": 494820, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-1-complete/evidence.json" + }, + "phase": "export", + "elapsed_ms": 77.37070700000004, + "reply_bytes": 352, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 656.71906, + "discovery_ms": 934.223808, + "runtime_validation_after_ms": 650.1014609999993 + }, + "wall_seconds": 5.761, + "cpu_seconds": 5.531688, + "kernel_family_peak_bytes": 723959808, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 54883, + "arguments": 67, + "text_replies": 73303, + "combined": 128858, + "complete_transport_catalog": 822529 + }, + "receipt_sha256": { + "report.json": "64954da0dbe2983fd6bd9ca31a1695f78cc414753c608b83adb1e696c33e5b4f", + "catalog.json": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "transcript.json": "2d5d6e2320b2d5cd781202b04cf48e2cc1a6e064df15cb354a273627fc914de5", + "evidence.json": "ec35d0dc74ceb39961fe57f8a282e97c1b710184ea1dea5cee17fa92853cac25", + "typing-1-complete.log": "1de7e2a794a87ddc9f920f12f1c35ccd939191a36f611791aa53c109d52a7d4f" + } + }, + { + "case": "typing-1-selected", + "lane": "selected", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc", + "bytes": 157945, + "sha256": "71f4d60de5be5a21d80b934a39dbc7c2ca541a36f8bb90a07f460c3ca2252f9a" + }, + "answer_count": 3, + "answer_sha256": "cc420a17dea4d43875fd64540d5f6bdad80af50c1d9090aa4d7cbd0f61099e18", + "parent_evidence_id": "ev_43fe9bc10193f43880ceb402d1790f3b97475c5d5b5510d22efebb56782efe41", + "source_report_sha256": "a851919d19f63cd64c339bd0d523eb3f3c95fcc96f8db7768c8e71b2bed0dc16", + "runtime_identity_sha256": "b3d2f7478b0b4aec9d60e8bb3ba0043c5b06a1da5da4a62c2392fac9f65f0780", + "catalog_sha256": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc", + "detail": "summary", + "view": { + "kind": "python-pyc", + "collection": "code_objects", + "names": ["get_type_hints", "_eval_type", "_type_check"], + "fields": [ + "name", + "qualname", + "filename", + "firstlineno", + "bytecode", + "nested_code" + ], + "offset": 0, + "limit": 64 + } + }, + "phase": "inspect", + "elapsed_ms": 2760.624337, + "reply_bytes": 8447, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-1-selected/evidence.json" + }, + "phase": "export", + "elapsed_ms": 58.158486999999695, + "reply_bytes": 352, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 632.9438620000001, + "discovery_ms": 914.4652690000001, + "runtime_validation_after_ms": 561.8325750000004 + }, + "wall_seconds": 5.56, + "cpu_seconds": 5.329382, + "kernel_family_peak_bytes": 672649216, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 55283, + "arguments": 125, + "text_replies": 1303, + "combined": 57316, + "complete_transport_catalog": 826451 + }, + "receipt_sha256": { + "report.json": "39f594ab06099a11a1387cb3d7119d5de8b8180109cfdd1c67c663af79f66226", + "catalog.json": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "transcript.json": "827fe23efcfc8d5e4b7fbfe4cb9d41cca80cca05e35ce960847a0289d0230a9c", + "evidence.json": "d085f798d92fd8be70fe75764be84b2dc9884925064a753333d7daffda511fb9", + "typing-1-selected.log": "a6483af8835e9707581899991c8b1b1ca81d92657222e2b3125d4540b83f3ee9" + } + }, + { + "case": "control-1-complete", + "lane": "complete", + "artifact": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc", + "bytes": 1396, + "sha256": "3dbccc61cbba13f44ad56489555eb92363eeedd92dd2dccaff7fc31088a5e136" + }, + "answer_count": 4, + "answer_sha256": "3486c42c2440e6e115931eef7f401635b2e3c3dee3a64ff8b9154ac5e40f9c44", + "parent_evidence_id": "ev_47853f6fecf57e6e880fab701dfb9809da5af7d8d0b44f89f53cc38a7ed62f65", + "source_report_sha256": "680e9e83bda96362902a697949fbd2aa3e69634c3a593c6f073ac22da3d97ef6", + "runtime_identity_sha256": "34d76c255cd82c1d261e8c53c222e7207616258a4d19796ea7545a714865b3d6", + "catalog_sha256": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc" + }, + "phase": "inspect", + "elapsed_ms": 2334.796977, + "reply_bytes": 14541, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-1-complete/evidence.json" + }, + "phase": "export", + "elapsed_ms": 69.83383700000013, + "reply_bytes": 350, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 669.480131, + "discovery_ms": 910.201789, + "runtime_validation_after_ms": 646.0092910000003 + }, + "wall_seconds": 5.258, + "cpu_seconds": 5.040796, + "kernel_family_peak_bytes": 676126720, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 91, + "server_instructions": 513, + "input_catalog_once": 54883, + "arguments": 66, + "text_replies": 2166, + "combined": 57719, + "complete_transport_catalog": 822529 + }, + "receipt_sha256": { + "report.json": "4b4f0385e4eabaa100d3a8cac6736102caa07a6bc53f5081bd8cd14f25ea69fe", + "catalog.json": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "transcript.json": "faa9d3094b234af7eca6444a0f28a17999b004e98b10cd39f505e66e842f3608", + "evidence.json": "2724d7c0189b92cccb2436d910c8e37624be2ef598059e62a14435a80c73f174", + "control-1-complete.log": "f337febcb0c25b9fca470e6200e7821cdcebedea23bac4d2c97a53716c4da85c" + } + }, + { + "case": "control-1-selected", + "lane": "selected", + "artifact": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc", + "bytes": 1396, + "sha256": "3dbccc61cbba13f44ad56489555eb92363eeedd92dd2dccaff7fc31088a5e136" + }, + "answer_count": 4, + "answer_sha256": "3486c42c2440e6e115931eef7f401635b2e3c3dee3a64ff8b9154ac5e40f9c44", + "parent_evidence_id": "ev_47853f6fecf57e6e880fab701dfb9809da5af7d8d0b44f89f53cc38a7ed62f65", + "source_report_sha256": "680e9e83bda96362902a697949fbd2aa3e69634c3a593c6f073ac22da3d97ef6", + "runtime_identity_sha256": "b3d2f7478b0b4aec9d60e8bb3ba0043c5b06a1da5da4a62c2392fac9f65f0780", + "catalog_sha256": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc", + "detail": "summary", + "view": { + "kind": "python-pyc", + "collection": "code_objects", + "names": ["parse_args", "parse_known_args", "_parse_known_args"], + "fields": [ + "name", + "qualname", + "filename", + "firstlineno", + "bytecode", + "nested_code" + ], + "offset": 0, + "limit": 64 + } + }, + "phase": "inspect", + "elapsed_ms": 2359.1729259999997, + "reply_bytes": 8731, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-1-selected/evidence.json" + }, + "phase": "export", + "elapsed_ms": 46.28745799999979, + "reply_bytes": 352, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 647.0545709999999, + "discovery_ms": 901.6756310000001, + "runtime_validation_after_ms": 629.8779419999992 + }, + "wall_seconds": 5.254, + "cpu_seconds": 4.98266, + "kernel_family_peak_bytes": 659181568, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 91, + "server_instructions": 513, + "input_catalog_once": 55283, + "arguments": 123, + "text_replies": 1322, + "combined": 57332, + "complete_transport_catalog": 826451 + }, + "receipt_sha256": { + "report.json": "28a0d3cb2e194320926a7f92c706aee31acd03c46aed3393df2ab00e6b5296da", + "catalog.json": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "transcript.json": "9ea13934100b202703d495ba1651bbafab5aa653ffcfec05b6f50e3ebb41b0ff", + "evidence.json": "7eb63d914d265eb926cd71dbcc82a32ed7e63906513986c5725b23c947f54012", + "control-1-selected.log": "b76f0886c368b27832f0e92054789134357204c34819e23b9a9fb119797dcbdf" + } + }, + { + "case": "argparse-2-selected", + "lane": "selected", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc", + "bytes": 113662, + "sha256": "6fa55dc31dd55ddb1766369755b5cda156a42cf41193a0ff1de0189bffb4aa51" + }, + "answer_count": 3, + "answer_sha256": "5a7282ec16a5d3e0ee1bfedf4a0c296939fd5af2efc36d758e028d7938f6a890", + "parent_evidence_id": "ev_11532cdd39935b8e7838d2f75d278672e4dbbe828b6daa2baf797d7fb5753064", + "source_report_sha256": "d62fc8622c35b44eb5e9caa41b272316542f53c16da92e870eff562d5924851f", + "runtime_identity_sha256": "b3d2f7478b0b4aec9d60e8bb3ba0043c5b06a1da5da4a62c2392fac9f65f0780", + "catalog_sha256": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc", + "detail": "summary", + "view": { + "kind": "python-pyc", + "collection": "code_objects", + "names": ["parse_args", "parse_known_args", "_parse_known_args"], + "fields": [ + "name", + "qualname", + "filename", + "firstlineno", + "bytecode", + "nested_code" + ], + "offset": 0, + "limit": 64 + } + }, + "phase": "inspect", + "elapsed_ms": 2787.070538, + "reply_bytes": 8655, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-2-selected/evidence.json" + }, + "phase": "export", + "elapsed_ms": 26.66675899999973, + "reply_bytes": 356, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 672.6285300000001, + "discovery_ms": 849.1731039999999, + "runtime_validation_after_ms": 622.8112030000002 + }, + "wall_seconds": 5.559, + "cpu_seconds": 5.368671, + "kernel_family_peak_bytes": 712794112, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 55283, + "arguments": 126, + "text_replies": 1286, + "combined": 57300, + "complete_transport_catalog": 826451 + }, + "receipt_sha256": { + "report.json": "a877bf36d46d0ead53f8a712feb1a61d7206402d8749c16bab301509dc3f8fd2", + "catalog.json": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "transcript.json": "ba7eb41cd70b2c328b384e9302cc63ebb5cbd5c624d2861707783e20f795b635", + "evidence.json": "6d509a2a1ee08bf5317d49ed1fb26a3cdf275814ffff0a2d183354a3a0941fa9", + "argparse-2-selected.log": "54f5a280a769f4364cdf01d294d7161942f8041413e128804ca8d58d50bc25f8" + } + }, + { + "case": "argparse-2-complete", + "lane": "complete", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc", + "bytes": 113662, + "sha256": "6fa55dc31dd55ddb1766369755b5cda156a42cf41193a0ff1de0189bffb4aa51" + }, + "answer_count": 3, + "answer_sha256": "5a7282ec16a5d3e0ee1bfedf4a0c296939fd5af2efc36d758e028d7938f6a890", + "parent_evidence_id": "ev_11532cdd39935b8e7838d2f75d278672e4dbbe828b6daa2baf797d7fb5753064", + "source_report_sha256": "d62fc8622c35b44eb5e9caa41b272316542f53c16da92e870eff562d5924851f", + "runtime_identity_sha256": "34d76c255cd82c1d261e8c53c222e7207616258a4d19796ea7545a714865b3d6", + "catalog_sha256": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc" + }, + "phase": "inspect", + "elapsed_ms": 2872.8026549999995, + "reply_bytes": 338090, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-2-complete/evidence.json" + }, + "phase": "export", + "elapsed_ms": 37.45862800000032, + "reply_bytes": 356, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 534.967527, + "discovery_ms": 1000.2115470000001, + "runtime_validation_after_ms": 695.3347290000002 + }, + "wall_seconds": 5.76, + "cpu_seconds": 5.577596, + "kernel_family_peak_bytes": 680681472, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 54883, + "arguments": 69, + "text_replies": 48858, + "combined": 104415, + "complete_transport_catalog": 822529 + }, + "receipt_sha256": { + "report.json": "0629762e0f340bd4942a74802790d7ff95eccf80f488f77f8f570e814d26b73c", + "catalog.json": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "transcript.json": "b253b9285cb419aac890e7cb8cf104e6c992fc2e72cebd04c77bc5fa4482945e", + "evidence.json": "6f0d4cc7722a4d4a7b37d7e7be9840a6ee800651e8c7f838b91aa8b48eaaa38b", + "argparse-2-complete.log": "7d416e62011de359c44256652d0d0be53b878eeb37ff2688d52186afac8f3adf" + } + }, + { + "case": "typing-2-selected", + "lane": "selected", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc", + "bytes": 157945, + "sha256": "71f4d60de5be5a21d80b934a39dbc7c2ca541a36f8bb90a07f460c3ca2252f9a" + }, + "answer_count": 3, + "answer_sha256": "cc420a17dea4d43875fd64540d5f6bdad80af50c1d9090aa4d7cbd0f61099e18", + "parent_evidence_id": "ev_43fe9bc10193f43880ceb402d1790f3b97475c5d5b5510d22efebb56782efe41", + "source_report_sha256": "a851919d19f63cd64c339bd0d523eb3f3c95fcc96f8db7768c8e71b2bed0dc16", + "runtime_identity_sha256": "b3d2f7478b0b4aec9d60e8bb3ba0043c5b06a1da5da4a62c2392fac9f65f0780", + "catalog_sha256": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc", + "detail": "summary", + "view": { + "kind": "python-pyc", + "collection": "code_objects", + "names": ["get_type_hints", "_eval_type", "_type_check"], + "fields": [ + "name", + "qualname", + "filename", + "firstlineno", + "bytecode", + "nested_code" + ], + "offset": 0, + "limit": 64 + } + }, + "phase": "inspect", + "elapsed_ms": 2819.9443490000003, + "reply_bytes": 8447, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-2-selected/evidence.json" + }, + "phase": "export", + "elapsed_ms": 616.0040939999999, + "reply_bytes": 352, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 677.991501, + "discovery_ms": 1009.254257, + "runtime_validation_after_ms": 709.1415299999999 + }, + "wall_seconds": 6.466, + "cpu_seconds": 6.249371, + "kernel_family_peak_bytes": 682242048, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 55283, + "arguments": 125, + "text_replies": 1303, + "combined": 57316, + "complete_transport_catalog": 826451 + }, + "receipt_sha256": { + "report.json": "a796320e48a323834e9152436f67a51e0b86fc6faaab8ccbe255544544032f15", + "catalog.json": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "transcript.json": "82dfdc1465081fc542f6e12455e389e1762fa5df135f050cf57f9982d45ae12a", + "evidence.json": "d085f798d92fd8be70fe75764be84b2dc9884925064a753333d7daffda511fb9", + "typing-2-selected.log": "29a84e9959ff828413076c5fde0c635a6c0f5db6c42df35945ba4509af8540ac" + } + }, + { + "case": "typing-2-complete", + "lane": "complete", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc", + "bytes": 157945, + "sha256": "71f4d60de5be5a21d80b934a39dbc7c2ca541a36f8bb90a07f460c3ca2252f9a" + }, + "answer_count": 3, + "answer_sha256": "cc420a17dea4d43875fd64540d5f6bdad80af50c1d9090aa4d7cbd0f61099e18", + "parent_evidence_id": "ev_43fe9bc10193f43880ceb402d1790f3b97475c5d5b5510d22efebb56782efe41", + "source_report_sha256": "a851919d19f63cd64c339bd0d523eb3f3c95fcc96f8db7768c8e71b2bed0dc16", + "runtime_identity_sha256": "34d76c255cd82c1d261e8c53c222e7207616258a4d19796ea7545a714865b3d6", + "catalog_sha256": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc" + }, + "phase": "inspect", + "elapsed_ms": 2901.5427059999997, + "reply_bytes": 494820, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-2-complete/evidence.json" + }, + "phase": "export", + "elapsed_ms": 30.161847999999736, + "reply_bytes": 352, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 698.24773, + "discovery_ms": 976.671468, + "runtime_validation_after_ms": 682.0609510000004 + }, + "wall_seconds": 5.963, + "cpu_seconds": 5.748996, + "kernel_family_peak_bytes": 700669952, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 54883, + "arguments": 67, + "text_replies": 73303, + "combined": 128858, + "complete_transport_catalog": 822529 + }, + "receipt_sha256": { + "report.json": "393f4942e728abbbdef220462604343662cd9250c2ac7c6526d7b02bf3460f47", + "catalog.json": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "transcript.json": "f92ed2b40f190af71be04ef677dd2a405d6b7e941f15b231e0390b8f870eb9d4", + "evidence.json": "ec35d0dc74ceb39961fe57f8a282e97c1b710184ea1dea5cee17fa92853cac25", + "typing-2-complete.log": "bdce6dc7fe2ac2e9e21f5967bbde61f70517af313939f5c25c58142c89be20c8" + } + }, + { + "case": "control-2-selected", + "lane": "selected", + "artifact": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc", + "bytes": 1396, + "sha256": "3dbccc61cbba13f44ad56489555eb92363eeedd92dd2dccaff7fc31088a5e136" + }, + "answer_count": 4, + "answer_sha256": "3486c42c2440e6e115931eef7f401635b2e3c3dee3a64ff8b9154ac5e40f9c44", + "parent_evidence_id": "ev_47853f6fecf57e6e880fab701dfb9809da5af7d8d0b44f89f53cc38a7ed62f65", + "source_report_sha256": "680e9e83bda96362902a697949fbd2aa3e69634c3a593c6f073ac22da3d97ef6", + "runtime_identity_sha256": "b3d2f7478b0b4aec9d60e8bb3ba0043c5b06a1da5da4a62c2392fac9f65f0780", + "catalog_sha256": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc", + "detail": "summary", + "view": { + "kind": "python-pyc", + "collection": "code_objects", + "names": ["parse_args", "parse_known_args", "_parse_known_args"], + "fields": [ + "name", + "qualname", + "filename", + "firstlineno", + "bytecode", + "nested_code" + ], + "offset": 0, + "limit": 64 + } + }, + "phase": "inspect", + "elapsed_ms": 2468.9457139999995, + "reply_bytes": 8731, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-2-selected/evidence.json" + }, + "phase": "export", + "elapsed_ms": 39.9303089999994, + "reply_bytes": 352, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 706.36913, + "discovery_ms": 995.836557, + "runtime_validation_after_ms": 676.849021 + }, + "wall_seconds": 5.558, + "cpu_seconds": 5.344233, + "kernel_family_peak_bytes": 686104576, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 91, + "server_instructions": 513, + "input_catalog_once": 55283, + "arguments": 123, + "text_replies": 1322, + "combined": 57332, + "complete_transport_catalog": 826451 + }, + "receipt_sha256": { + "report.json": "e237ed481e6a4373056434579244acd05cb7ef1417a5ce0a79433da776226f4b", + "catalog.json": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "transcript.json": "300928ad81f2d3fad6e1e095037209ce894d660defb60417e8be9bc16d5119bc", + "evidence.json": "7eb63d914d265eb926cd71dbcc82a32ed7e63906513986c5725b23c947f54012", + "control-2-selected.log": "4a733ad6549f76dc95a483dd5dfbd53d294377d329de1b155f6318823a160b2f" + } + }, + { + "case": "control-2-complete", + "lane": "complete", + "artifact": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc", + "bytes": 1396, + "sha256": "3dbccc61cbba13f44ad56489555eb92363eeedd92dd2dccaff7fc31088a5e136" + }, + "answer_count": 4, + "answer_sha256": "3486c42c2440e6e115931eef7f401635b2e3c3dee3a64ff8b9154ac5e40f9c44", + "parent_evidence_id": "ev_47853f6fecf57e6e880fab701dfb9809da5af7d8d0b44f89f53cc38a7ed62f65", + "source_report_sha256": "680e9e83bda96362902a697949fbd2aa3e69634c3a593c6f073ac22da3d97ef6", + "runtime_identity_sha256": "34d76c255cd82c1d261e8c53c222e7207616258a4d19796ea7545a714865b3d6", + "catalog_sha256": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc" + }, + "phase": "inspect", + "elapsed_ms": 2539.611843, + "reply_bytes": 14541, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-2-complete/evidence.json" + }, + "phase": "export", + "elapsed_ms": 20.915498999999727, + "reply_bytes": 350, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 739.8303289999999, + "discovery_ms": 989.4525990000001, + "runtime_validation_after_ms": 683.4660010000007 + }, + "wall_seconds": 5.562, + "cpu_seconds": 5.365922, + "kernel_family_peak_bytes": 674852864, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 91, + "server_instructions": 513, + "input_catalog_once": 54883, + "arguments": 66, + "text_replies": 2166, + "combined": 57719, + "complete_transport_catalog": 822529 + }, + "receipt_sha256": { + "report.json": "ae4e1f42b40431d6486f4b887ff7272552f91cf97c28daa71103d99afa500b89", + "catalog.json": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "transcript.json": "ce4ed666f4df21b78305695e9c81ce7f7b33ddaa4d698d6cbce3f970686c32bf", + "evidence.json": "2724d7c0189b92cccb2436d910c8e37624be2ef598059e62a14435a80c73f174", + "control-2-complete.log": "6ccfd18b0cf873cf937b71178298653194bf2be743f5a73b2eef35d5b1a9bb96" + } + }, + { + "case": "argparse-3-complete", + "lane": "complete", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc", + "bytes": 113662, + "sha256": "6fa55dc31dd55ddb1766369755b5cda156a42cf41193a0ff1de0189bffb4aa51" + }, + "answer_count": 3, + "answer_sha256": "5a7282ec16a5d3e0ee1bfedf4a0c296939fd5af2efc36d758e028d7938f6a890", + "parent_evidence_id": "ev_11532cdd39935b8e7838d2f75d278672e4dbbe828b6daa2baf797d7fb5753064", + "source_report_sha256": "d62fc8622c35b44eb5e9caa41b272316542f53c16da92e870eff562d5924851f", + "runtime_identity_sha256": "34d76c255cd82c1d261e8c53c222e7207616258a4d19796ea7545a714865b3d6", + "catalog_sha256": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc" + }, + "phase": "inspect", + "elapsed_ms": 2753.8989840000004, + "reply_bytes": 338090, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-3-complete/evidence.json" + }, + "phase": "export", + "elapsed_ms": 75.97511699999995, + "reply_bytes": 356, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 711.8488609999999, + "discovery_ms": 1006.2277969999999, + "runtime_validation_after_ms": 560.5551169999999 + }, + "wall_seconds": 5.761, + "cpu_seconds": 5.531869, + "kernel_family_peak_bytes": 681426944, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 54883, + "arguments": 69, + "text_replies": 48858, + "combined": 104415, + "complete_transport_catalog": 822529 + }, + "receipt_sha256": { + "report.json": "587f0cd6f5a771ce68dca98a8467b48096f7de6b60189a63b0b0a4bd3ce7c972", + "catalog.json": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "transcript.json": "880e364975fa81b5c2e9a31980fd3bea87fbcc53f2f6dc96bfb4d336728bbc99", + "evidence.json": "6f0d4cc7722a4d4a7b37d7e7be9840a6ee800651e8c7f838b91aa8b48eaaa38b", + "argparse-3-complete.log": "f8d1ef2ec6ea058c0d5a9fdd2050267ce0af4c597a2df17e28154874132aac43" + } + }, + { + "case": "argparse-3-selected", + "lane": "selected", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc", + "bytes": 113662, + "sha256": "6fa55dc31dd55ddb1766369755b5cda156a42cf41193a0ff1de0189bffb4aa51" + }, + "answer_count": 3, + "answer_sha256": "5a7282ec16a5d3e0ee1bfedf4a0c296939fd5af2efc36d758e028d7938f6a890", + "parent_evidence_id": "ev_11532cdd39935b8e7838d2f75d278672e4dbbe828b6daa2baf797d7fb5753064", + "source_report_sha256": "d62fc8622c35b44eb5e9caa41b272316542f53c16da92e870eff562d5924851f", + "runtime_identity_sha256": "b3d2f7478b0b4aec9d60e8bb3ba0043c5b06a1da5da4a62c2392fac9f65f0780", + "catalog_sha256": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/argparse.cpython-311.pyc", + "detail": "summary", + "view": { + "kind": "python-pyc", + "collection": "code_objects", + "names": ["parse_args", "parse_known_args", "_parse_known_args"], + "fields": [ + "name", + "qualname", + "filename", + "firstlineno", + "bytecode", + "nested_code" + ], + "offset": 0, + "limit": 64 + } + }, + "phase": "inspect", + "elapsed_ms": 2677.430598, + "reply_bytes": 8655, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/argparse-3-selected/evidence.json" + }, + "phase": "export", + "elapsed_ms": 58.59457800000018, + "reply_bytes": 356, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 636.3470140000001, + "discovery_ms": 928.530071, + "runtime_validation_after_ms": 654.339363 + }, + "wall_seconds": 5.562, + "cpu_seconds": 5.378098, + "kernel_family_peak_bytes": 663261184, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 55283, + "arguments": 126, + "text_replies": 1286, + "combined": 57300, + "complete_transport_catalog": 826451 + }, + "receipt_sha256": { + "report.json": "bf055f03958eec8244db2dd4a25b8e72d238670d7c7e526d3b85ea31246fe001", + "catalog.json": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "transcript.json": "8e1ee8f578367ba249184fb4177d09852c190e7e58ac0494123aa9edb813db26", + "evidence.json": "6d509a2a1ee08bf5317d49ed1fb26a3cdf275814ffff0a2d183354a3a0941fa9", + "argparse-3-selected.log": "b9315a8720983873456fbf70efd11602c460ed6e3ad0b4b8ca9bc1c3149e3957" + } + }, + { + "case": "typing-3-complete", + "lane": "complete", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc", + "bytes": 157945, + "sha256": "71f4d60de5be5a21d80b934a39dbc7c2ca541a36f8bb90a07f460c3ca2252f9a" + }, + "answer_count": 3, + "answer_sha256": "cc420a17dea4d43875fd64540d5f6bdad80af50c1d9090aa4d7cbd0f61099e18", + "parent_evidence_id": "ev_43fe9bc10193f43880ceb402d1790f3b97475c5d5b5510d22efebb56782efe41", + "source_report_sha256": "a851919d19f63cd64c339bd0d523eb3f3c95fcc96f8db7768c8e71b2bed0dc16", + "runtime_identity_sha256": "34d76c255cd82c1d261e8c53c222e7207616258a4d19796ea7545a714865b3d6", + "catalog_sha256": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc" + }, + "phase": "inspect", + "elapsed_ms": 2740.3405169999996, + "reply_bytes": 494820, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-3-complete/evidence.json" + }, + "phase": "export", + "elapsed_ms": 33.467509000000064, + "reply_bytes": 352, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 648.6301530000001, + "discovery_ms": 915.630442, + "runtime_validation_after_ms": 632.1289740000002 + }, + "wall_seconds": 5.66, + "cpu_seconds": 5.43948, + "kernel_family_peak_bytes": 718323712, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 54883, + "arguments": 67, + "text_replies": 73303, + "combined": 128858, + "complete_transport_catalog": 822529 + }, + "receipt_sha256": { + "report.json": "eaee19c0c99bdd5235cfa228dc9f6b71bcd1ae08985cd7497158fed03fbd8241", + "catalog.json": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "transcript.json": "a932411672a73e57dcae81119dd735cf4e6ff668c656dd7fdf751436281aa7e9", + "evidence.json": "ec35d0dc74ceb39961fe57f8a282e97c1b710184ea1dea5cee17fa92853cac25", + "typing-3-complete.log": "f17bebc7aa9971786bc8fefdd1cc24c5a9020df622183abe8eef60e2fc0a53ce" + } + }, + { + "case": "typing-3-selected", + "lane": "selected", + "artifact": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc", + "bytes": 157945, + "sha256": "71f4d60de5be5a21d80b934a39dbc7c2ca541a36f8bb90a07f460c3ca2252f9a" + }, + "answer_count": 3, + "answer_sha256": "cc420a17dea4d43875fd64540d5f6bdad80af50c1d9090aa4d7cbd0f61099e18", + "parent_evidence_id": "ev_43fe9bc10193f43880ceb402d1790f3b97475c5d5b5510d22efebb56782efe41", + "source_report_sha256": "a851919d19f63cd64c339bd0d523eb3f3c95fcc96f8db7768c8e71b2bed0dc16", + "runtime_identity_sha256": "b3d2f7478b0b4aec9d60e8bb3ba0043c5b06a1da5da4a62c2392fac9f65f0780", + "catalog_sha256": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/usr/lib/python3.11/__pycache__/typing.cpython-311.pyc", + "detail": "summary", + "view": { + "kind": "python-pyc", + "collection": "code_objects", + "names": ["get_type_hints", "_eval_type", "_type_check"], + "fields": [ + "name", + "qualname", + "filename", + "firstlineno", + "bytecode", + "nested_code" + ], + "offset": 0, + "limit": 64 + } + }, + "phase": "inspect", + "elapsed_ms": 2881.8394120000003, + "reply_bytes": 8447, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/typing-3-selected/evidence.json" + }, + "phase": "export", + "elapsed_ms": 61.41834699999981, + "reply_bytes": 352, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 657.8178429999999, + "discovery_ms": 891.633754, + "runtime_validation_after_ms": 658.6261039999999 + }, + "wall_seconds": 5.761, + "cpu_seconds": 5.566434, + "kernel_family_peak_bytes": 673751040, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 92, + "server_instructions": 513, + "input_catalog_once": 55283, + "arguments": 125, + "text_replies": 1303, + "combined": 57316, + "complete_transport_catalog": 826451 + }, + "receipt_sha256": { + "report.json": "81d71c5560c0c3ef72efcc2a32a7f419550477347b0d0ac7a88b7141dbc2b99d", + "catalog.json": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "transcript.json": "8067d5202b487f9dbba010ad7fc5593aad335b5a13d4b10794808cc2fa3915b7", + "evidence.json": "d085f798d92fd8be70fe75764be84b2dc9884925064a753333d7daffda511fb9", + "typing-3-selected.log": "99a3df40f0b3ec9dc7a52a0f3f29edfdaeb8cb36120dadc84cc23ccdb84acb90" + } + }, + { + "case": "control-3-complete", + "lane": "complete", + "artifact": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc", + "bytes": 1396, + "sha256": "3dbccc61cbba13f44ad56489555eb92363eeedd92dd2dccaff7fc31088a5e136" + }, + "answer_count": 4, + "answer_sha256": "3486c42c2440e6e115931eef7f401635b2e3c3dee3a64ff8b9154ac5e40f9c44", + "parent_evidence_id": "ev_47853f6fecf57e6e880fab701dfb9809da5af7d8d0b44f89f53cc38a7ed62f65", + "source_report_sha256": "680e9e83bda96362902a697949fbd2aa3e69634c3a593c6f073ac22da3d97ef6", + "runtime_identity_sha256": "34d76c255cd82c1d261e8c53c222e7207616258a4d19796ea7545a714865b3d6", + "catalog_sha256": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc" + }, + "phase": "inspect", + "elapsed_ms": 2559.1234249999998, + "reply_bytes": 14541, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-3-complete/evidence.json" + }, + "phase": "export", + "elapsed_ms": 71.17073699999946, + "reply_bytes": 350, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 642.194464, + "discovery_ms": 896.482364, + "runtime_validation_after_ms": 613.0741049999997 + }, + "wall_seconds": 5.358, + "cpu_seconds": 5.180368, + "kernel_family_peak_bytes": 694022144, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 91, + "server_instructions": 513, + "input_catalog_once": 54883, + "arguments": 66, + "text_replies": 2166, + "combined": 57719, + "complete_transport_catalog": 822529 + }, + "receipt_sha256": { + "report.json": "8831ceffa302ce3f7666b59d2d56a55f0ab2cff7d35538a3fe14bc3e1b2d153b", + "catalog.json": "2d86e7a0a49b121ab828d993424f75cf7b10c9f49246a5bbc890205d1a030a0d", + "transcript.json": "0b0900da8ce403b1b54d5e312c007b1f0bfff2036dbe467b8bd5b27bd2096485", + "evidence.json": "2724d7c0189b92cccb2436d910c8e37624be2ef598059e62a14435a80c73f174", + "control-3-complete.log": "057fa1d4e27449f96543475998455059c201051cbabe778366896029242c1374" + } + }, + { + "case": "control-3-selected", + "lane": "selected", + "artifact": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc", + "bytes": 1396, + "sha256": "3dbccc61cbba13f44ad56489555eb92363eeedd92dd2dccaff7fc31088a5e136" + }, + "answer_count": 4, + "answer_sha256": "3486c42c2440e6e115931eef7f401635b2e3c3dee3a64ff8b9154ac5e40f9c44", + "parent_evidence_id": "ev_47853f6fecf57e6e880fab701dfb9809da5af7d8d0b44f89f53cc38a7ed62f65", + "source_report_sha256": "680e9e83bda96362902a697949fbd2aa3e69634c3a593c6f073ac22da3d97ef6", + "runtime_identity_sha256": "b3d2f7478b0b4aec9d60e8bb3ba0043c5b06a1da5da4a62c2392fac9f65f0780", + "catalog_sha256": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "calls": [ + { + "name": "inspect_python_pyc", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/control/control.pyc", + "detail": "summary", + "view": { + "kind": "python-pyc", + "collection": "code_objects", + "names": ["parse_args", "parse_known_args", "_parse_known_args"], + "fields": [ + "name", + "qualname", + "filename", + "firstlineno", + "bytecode", + "nested_code" + ], + "offset": 0, + "limit": 64 + } + }, + "phase": "inspect", + "elapsed_ms": 2647.0018029999997, + "reply_bytes": 8731, + "is_error": false + }, + { + "name": "export_evidence_bundle", + "input": { + "path": "/tmp/rea-perf-pyc-views-20261023/export-cohort/control-3-selected/evidence.json" + }, + "phase": "export", + "elapsed_ms": 20.188428999999815, + "reply_bytes": 352, + "is_error": false + } + ], + "phases": { + "runtime_validation_before_ms": 644.1214540000001, + "discovery_ms": 919.126363, + "runtime_validation_after_ms": 630.7684049999998 + }, + "wall_seconds": 5.459, + "cpu_seconds": 5.246611, + "kernel_family_peak_bytes": 691466240, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + }, + "payload_cleanup_verified": true, + "final_whole_scope_events": null, + "token_proxy": { + "question": 91, + "server_instructions": 513, + "input_catalog_once": 55283, + "arguments": 123, + "text_replies": 1322, + "combined": 57332, + "complete_transport_catalog": 826451 + }, + "receipt_sha256": { + "report.json": "245e4a1a2c4d8c57571229c81053f93693e734db342e969069a19316a39cca91", + "catalog.json": "a56fcc96673156d115979417e999ec4397ed7dc3f414211e9e118cc20a8856ff", + "transcript.json": "3787c4a45621c3dd9eb0c096dea8505985d0e353ff9fb8e4f4bf026fec80645e", + "evidence.json": "7eb63d914d265eb926cd71dbcc82a32ed7e63906513986c5725b23c947f54012", + "control-3-selected.log": "94e105b45b906b8d9ac5df75949ae9840ab1c5c873128eddba7d501e915fc1bf" + } + } + ] +} diff --git a/experiments/analysis-efficiency/python-pyc-views.md b/experiments/analysis-efficiency/python-pyc-views.md new file mode 100644 index 0000000000..23f6b9c09c --- /dev/null +++ b/experiments/analysis-efficiency/python-pyc-views.md @@ -0,0 +1,59 @@ +# Selected Python pyc facts + +Issue [#6922](https://github.com/morluto/rea/issues/6922) asks for selected code-object facts without delivering the whole report. The existing static parser already owns the facts. The change adds a typed `python-pyc` view to the shared retained-Evidence projector, and opt-in `detail: "summary", view: {...}` delivery through the existing CLI/MCP tool. The default complete result and parent Evidence identity remain unchanged. + +Names, original indexes, fields and matching-row pages compose. Every same-name alternative survives under its parent-scoped `source_index`; an empty selector selects nothing. `nested_code` keeps original code-constant reference indexes and does not claim runtime calls. Source coverage is separate from page coverage. Complete Evidence remains exportable, and saved bundles support offline views after the original file is deleted. + +The immutable-result reader is shared with native-export views instead of adding a second cache implementation. Each owning schema has a weak cache keyed by the validated, owner-sealed result object. It never caches a path or current filesystem state. Mutable/unowned values and failed parses are revalidated; schema-normalized facts stay separate from the source. CLI delivery JSON parsing also has one owner, shared with the existing JavaScript route commands. + +## Question and independent oracle + +For each target, return every code object with one of three exact names, including `source_index`, `name`, `qualname`, `filename`, `firstlineno`, bytecode byte length/SHA-256 and `nested_code`. Preserve source coverage and export the complete source Evidence. Both lanes perform discovery, one inspection and one Evidence export. The selected lane includes the view in the first inspection; this question fits one page with the default limit of 64. + +| Input | Source bytes | All objects | Selected rows | Names | +| ----------------------------------------- | -----------: | ----------: | ------------: | ----------------------------------------------------------- | +| CPython 3.11.2 `argparse.cpython-311.pyc` | 113,662 | 194 | 3 | `parse_args`, `parse_known_args`, `_parse_known_args` | +| CPython 3.11.2 `typing.cpython-311.pyc` | 157,945 | 307 | 3 | `get_type_hints`, `_eval_type`, `_type_check` | +| Inert compiler-produced control | 1,396 | 8 | 4 | argparse names, including two distinct `parse_args` objects | + +`python-pyc-oracle.py` uses the matching CPython 3.11.2 magic and an independent marshal/CodeType graph walk. It checks the selected facts for **all** objects in the exported source, as well as every returned answer row. The small control is compiled, never executed. The production lanes never execute target bytecode. The original oracle CLI imported the system argparse module as its argument-parser dependency (outside timed tasks); it did not explicitly execute the selected marshal CodeType, but the broader claim that the oracle never loaded target bytecode was inaccurate. Review replaced that dependency with `getopt`, added before/after guards for argparse/typing module loading, and regenerated all three oracles: their complete JSON facts are identical. The current oracle neither imports either selected standard-library module nor executes the decoded CodeType. At `062f94ce3`, a newly compiled control reproduces the original code facts and pyc SHA-256; all three selected stdio/export tasks pass again with the regenerated oracles and a current runtime/driver identity manifest. These observations qualify the three concrete CPython 3.11 artifacts, not every supported historical marshal layout or an agent's investigation quality. + +Target SHA-256 values, full commands, runtime/driver identities and receipt digests are in [the machine-readable results](python-pyc-views.json). All six sessions per target produce the same answer digest, parent Evidence ID and complete normalized-source digest. + +## Fixed-version measurements + +Baseline: `6b3eee1ccf4c51f23bef7a0007c7b8e892f662db`. Candidate: `29ff0893e` (full revision in the results). Three alternating fresh sessions per lane and target, 18 completed tasks. The order reverses in the second pair. Linux x86-64, Node 24.22.1, npm 11.16.0; production stdio MCP with SDK 2.3.1, its exact discovered tool definitions and default output validator. No validator patch or private SDK hook is used. + +Each task has a fresh HOME/client/server, disables Node compile caches, and leaves OS caches unflushed. These are fresh-session measurements with warm/uncontrolled OS caches; there is no separate warm-server cohort. Runtime files, installed client modules, entrypoints, the Node executable and driver files are checked before/after tasks. Oracle generation and tokenization are outside timed tasks. Whole-task costs include discovery, SDK schema compilation, runtime checks, source hashing, comparisons, serialization, export and cleanup. + +| Median | argparse complete → selected | typing complete → selected | Small control complete → selected | +| ---------------------------------------------------- | ---------------------------: | -------------------------: | --------------------------------: | +| Estimated task tokens | 104,415 → 57,300 | 128,858 → 57,316 | 57,719 → 57,332 | +| Serialized MCP reply bytes, both calls | 338,446 → 9,011 | 495,172 → 8,799 | 14,891 → 9,083 | +| Elapsed seconds | 5.760 → 5.559 | 5.761 → 5.761 | 5.358 → 5.459 | +| Process-family CPU seconds | 5.532 → 5.369 | 5.532 → 5.566 | 5.180 → 5.247 | +| Kernel process-family peak MiB, including file cache | 649.1 → 663.7 | 685.0 → 642.5 | 644.8 → 654.3 | + +The declared token projection falls **45.1%** for argparse and **55.5%** for typing, versus **0.7%** for the small control. No stable general elapsed-time, CPU or memory improvement is established. The parser and complete-source retention still do their full work. Wall-time ranges overlap for every target; typing selected ranges 5.560–6.466 seconds versus 5.660–5.963 complete. Other ranges and individual samples remain in the results. + +Token estimates use `tiktoken==0.14.0` / `cl100k_base`. The projection counts the question, server instructions, all 221 discovered tool names/descriptions/input schemas once, every argument and every MCP text reply, including export. Candidate input-directory cost increases from 54,883 to 55,283 estimated tokens. Complete raw catalog transport, including output schemas, is separately counted: 822,529 → 826,451 tokens. It is not added to the model-input directory projection. Structured content is validated and retained; the context projection counts the text reply once. Actual model usage, inference quality, history replay, protocol framing and host-added context are unmeasured. + +Each task uses one aggregate CPU quota, payload affinity CPUs 0–1, a 3 GiB early stop, 4 GiB hard limit, no swap and 192-task limit. The observer initially runs on CPU 15; the receipt's initial affinity does not describe payload placement. All 18 payloads exit zero, confirm cleanup, and show zero memory/task limit or OOM events at payload exit. Final whole-scope counters are unavailable and remain null. Peak kernel memory includes file cache and differs from a single process's RSS. + +## Verification and integration scope + +Executed against the measured base integration: + +- 46 source tests: analysis views, schema-specific immutable-result ownership, PYC delivery and existing JavaScript delivery contracts. Converted input schemas validate under draft-2020-12 and draft-07 and agree with runtime parsing on valid/invalid combinations. +- 27 public CLI/MCP tests across PYC and native-export suites: direct selected delivery, full source bundles, retained/offline queries after deleting the source, duplicate names, paging, empty/intersecting selectors, source references, view digests, invalid delivery and existing complete PYC behavior. +- Six serialized static tasks (typecheck, lint, format, unused exports, metadata and their compile dependency) and architecture guards passed. Test-family peak was about 2.48 GiB, with no guard stop/OOM and verified payload cleanup. + +The candidate subsequently integrated main `76500f12b54e07b1ea700a403b2593890d63b42a`. The table remains a measurement of the fixed revisions above. At integration `c172fd330`, `npm run docs:generate` and `npm run docs:check` passed (including 9 README consistency tests); the six serialized static tasks and architecture guards passed again, and the two affected retained-view public suites passed all 5 tests. The combined integration job peaked at about 2.59 GiB with verified payload cleanup and no resource stop/OOM. Real-artifact qualification is recorded separately; it does not turn these timings into measurements of a later revision. + +Retained failed attempts: an initial test mislabeled magic 3531 as 3.11 (the owner maps it to 3.12); the assertion now compares the complete owner's header. A CLI validation return initially bypassed `logCliCommand`, producing exit zero; validation now runs inside that command owner, before opening the provider, and the public negative regression passes. An oversized acceptance function was split into fixture/selector helpers. A final test invocation used a nonexistent JavaScript test filename and was rejected before running tests; the corrected invocation produced the 46/27 passing results above. An early cached build had an unverified transient cleanup observation; its scope was subsequently inactive. The final baseline build and all measured tasks verify payload cleanup. None of these failed attempts are counted as successful measurements. + +## Reproduction + +Use the pinned toolchain and dependencies. Build detached baseline and candidate runtimes, and generate independent facts with `python3 python-pyc-oracle.py --path ABSOLUTE_PYC --name NAME (repeatable) --output ORACLE.json`; `--compile-control NEW_DIRECTORY` creates the inert small control. Run `python-pyc-views.mjs --runtime RUNTIME --oracle ORACLE --output NEW_OUTPUT --lane complete|selected`, through `resource-run.py --observe-payload-exit` and the recorded affinity/toolchain wrapper. The optional `--identity` manifest pins compiled files/entrypoints, the Node executable and driver files; the before/after checks are included in task costs. Preserve raw catalog, transcript, complete bundle, report and guard log for every run. + +Repeat the recorded alternating order. A cohort manifest binds runtime identities and driver hashes; its index retains each exact command and exit. Aggregate with `uv run --no-project --with tiktoken==0.14.0 python python-pyc-costs.py --cohort COHORT --output RESULTS.json`. The aggregator rejects missing/failed tasks, failed cleanup/resource observations, changed identities, unequal answers/source digests and unexpected call counts. Raw local receipts are under `/tmp/rea-perf-pyc-views-20261023/export-cohort`; they are not required runtime files. diff --git a/experiments/analysis-efficiency/python-pyc-views.mjs b/experiments/analysis-efficiency/python-pyc-views.mjs new file mode 100644 index 0000000000..445e2a4ede --- /dev/null +++ b/experiments/analysis-efficiency/python-pyc-views.mjs @@ -0,0 +1,272 @@ +/** Same selected code-object question through production stdio MCP delivery. */ +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: { + runtime: { type: "string" }, + identity: { type: "string" }, + oracle: { type: "string" }, + output: { type: "string" }, + lane: { type: "string", default: "complete" }, + "page-size": { type: "string", default: "64" }, + "two-step": { type: "boolean", default: false }, + "probe-support": { type: "boolean", default: false }, + }, +}); +for (const key of ["runtime", "oracle", "output"]) + assert.ok(values[key] && values[key] === resolve(values[key]), key); +assert.ok(["complete", "selected"].includes(values.lane)); +const pageSize = Number(values["page-size"]); +assert.ok(Number.isSafeInteger(pageSize) && pageSize > 0); +const expected = JSON.parse(await readFile(values.oracle, "utf8")); +const hash = (value) => createHash("sha256").update(value).digest("hex"); +assert.equal( + hash(await readFile(expected.artifact.path)), + expected.artifact.sha256, +); +await mkdir(values.output, { mode: 0o700 }); +const account = await mkdtemp(join(tmpdir(), "rea-pyc-views-")); +const client = new Client({ name: "pyc-views", 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", + lane: values.lane, + artifact: expected.artifact, + selected_names: expected.names, + calls: [], + phases: {}, + client: "SDK exact discovered toolDefinition, default validator", + task: "Return every matching code object for exact names with original indices and selected fields; export complete source Evidence.", + bytecode_execution: false, + page_size: pageSize, + direct_first_view: values.lane === "selected" && !values["two-step"], +}; +const verifyRuntime = async (phase) => { + if (values.identity === undefined) return; + const started = performance.now(); + const bytes = await readFile(values.identity); + const identity = JSON.parse(bytes); + for (const entry of [...identity.compiled_files, ...identity.entrypoints]) + assert.equal( + hash(await readFile(join(values.runtime, entry.path))), + entry.sha256, + entry.path, + ); + for (const entry of identity.driver_files) + assert.equal( + hash(await readFile(new URL(entry.path, import.meta.url))), + entry.sha256, + ); + assert.equal(hash(await readFile(process.execPath)), identity.node_sha256); + report.runtime_identity_sha256 ??= hash(bytes); + assert.equal(hash(bytes), report.runtime_identity_sha256); + report.phases[`runtime_validation_${phase}_ms`] = performance.now() - started; +}; +const transcript = []; +let definitions; +const caller = 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, input, phase) => { + const result = await caller(name, input, phase); + if (result.isError === true) assert.fail(JSON.stringify(result)); + assert.ok(result.structuredContent); + return result.structuredContent; +}; +const columns = new Set(["source_index", ...expected.fields]); +const select = (row) => + Object.fromEntries(Object.entries(row).filter(([key]) => columns.has(key))); +try { + await verifyRuntime("before"); + const started = 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() - started; + report.catalog_tools = catalog.tools.length; + const selected = values.lane === "selected"; + const selectedView = (offset) => ({ + kind: "python-pyc", + collection: "code_objects", + names: expected.names, + fields: expected.fields, + offset, + limit: pageSize, + }); + const response = await observe( + "inspect_python_pyc", + { + path: expected.artifact.path, + ...(selected + ? { + detail: "summary", + ...(values["two-step"] ? {} : { view: selectedView(0) }), + } + : {}), + }, + "inspect", + ); + const parentId = selected + ? response.normalized_result.parent_evidence_id + : response.evidence_id; + report.parent_evidence_id = parentId; + let answer; + if (!selected) { + const source = response.normalized_result; + assert.deepEqual(source.artifact, expected.artifact); + assert.equal(source.coverage.status, "complete"); + const rows = source.code_objects.map((row, source_index) => + select({ ...row, source_index }), + ); + assert.deepEqual(rows, expected.rows); + answer = rows.filter((row) => expected.names.includes(row.name)); + } else { + answer = []; + let offset = 0; + let page = values["two-step"] ? undefined : response.normalized_result; + do { + page ??= ( + await observe( + "inspect_analysis_view", + { + source: { kind: "retained-evidence", evidence_id: parentId }, + view: selectedView(offset), + }, + "selected-page", + ) + ).normalized_result; + assert.equal(page.kind, "python-pyc"); + assert.deepEqual(page.pyc.artifact, expected.artifact); + assert.equal(page.pyc.coverage.status, "complete"); + assert.equal(page.pyc.coverage.code_objects, expected.rows.length); + assert.equal(page.coverage.total, expected.answer.length); + answer.push(...page.items); + if (page.coverage.next_offset === null) break; + assert.ok(page.coverage.next_offset > offset); + assert.ok(page.coverage.next_offset <= expected.answer.length); + offset = page.coverage.next_offset; + page = undefined; + } while (true); + } + assert.deepEqual(answer, expected.answer); + report.answer_sha256 = hash(JSON.stringify(answer)); + report.answer_count = answer.length; + await writeFile(join(values.output, "answer.json"), JSON.stringify(answer)); + await observe( + "export_evidence_bundle", + { path: join(values.output, "evidence.json") }, + "export", + ); + const bundle = JSON.parse( + await readFile(join(values.output, "evidence.json"), "utf8"), + ); + const parent = bundle.records.find( + (record) => record.evidence_id === parentId, + ); + assert.ok(parent); + assert.equal(parent.operation, "inspect_python_pyc"); + assert.deepEqual(parent.normalized_result.artifact, expected.artifact); + assert.equal(parent.normalized_result.coverage.status, "complete"); + assert.deepEqual( + parent.normalized_result.code_objects.map((row, source_index) => + select({ ...row, source_index }), + ), + expected.rows, + ); + report.source_report_sha256 = hash(JSON.stringify(parent.normalized_result)); + report.complete_source_export_verified = true; + if (values["probe-support"]) { + report.support = {}; + for (const [label, name, input] of [ + [ + "summary_delivery", + "inspect_python_pyc", + { path: expected.artifact.path, detail: "summary" }, + ], + [ + "summary_view", + "inspect_analysis_view", + { + source: { kind: "retained-evidence", evidence_id: parentId }, + view: { kind: "summary" }, + }, + ], + ]) { + try { + const reply = await caller(name, input, "capability-probe"); + report.support[label] = { is_error: reply.isError === true, reply }; + } catch (cause) { + report.support[label] = { is_error: true, message: String(cause) }; + } + } + } + report.status = "passed"; +} catch (cause) { + report.status = "failed"; + report.error = String(cause); +} finally { + try { + await verifyRuntime("after"); + } catch (cause) { + report.status = "failed"; + report.runtime_identity_failure = String(cause); + } + await finalizeRetainedQueryReceipt({ + client, + transport, + serverLog, + serverLogFinished, + account, + report, + output: values.output, + transcript, + }); + if ( + report.status !== "passed" || + report.cleanup_failures.length || + report.receipt_failures.length + ) + process.exitCode = 1; +} diff --git a/src/cli/analysisEvidenceDelivery.ts b/src/cli/analysisEvidenceDelivery.ts index d4906ed7ee..f000ac3830 100644 --- a/src/cli/analysisEvidenceDelivery.ts +++ b/src/cli/analysisEvidenceDelivery.ts @@ -5,7 +5,7 @@ import { parseEvidence, type Evidence } from "../domain/evidence.js"; import type { JsonValue } from "../domain/jsonValue.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; import type { AnalysisError } from "../domain/analysisErrorBase.js"; -import { ok, type Result } from "../domain/result.js"; +import { err, ok, type Result } from "../domain/result.js"; import { AnalysisInputError } from "../domain/analysisErrorCore.js"; import { createEvidenceBundle } from "../domain/evidenceBundle.js"; import { publishEvidenceDelivery } from "../application/EvidenceBundleCommands.js"; @@ -13,6 +13,8 @@ import { inspectAnalysisViewValidated } from "../application/analysisView/Analys import { localPathStringSchema } from "../domain/localPath.js"; import { absoluteCallerPath } from "../filesystem/callerPath.js"; import { finishAnalysisCommand } from "./finishAnalysisCommand.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; +import type { ZodSafeParseResult } from "zod"; export const analysisEvidenceDeliveryOptions = { detail: z @@ -58,6 +60,54 @@ export const cliEvidenceDeliveryInputError = ( ]) : undefined; +/** Parse delivery JSON once and enforce its owning schema before opening a target. */ +export const parseCliAnalysisEvidenceDelivery = < + Delivery extends { + readonly detail: "complete" | "summary"; + readonly view?: AnalysisViewRequest | undefined; + }, +>( + operation: string, + options: { + readonly detail: "complete" | "summary"; + readonly evidence_output?: string | undefined; + readonly view?: string | undefined; + }, + schema: { + readonly safeParse: (input: unknown) => ZodSafeParseResult; + }, +): Result< + Delivery & { readonly evidenceOutput: string | undefined }, + AnalysisError +> => { + let view: unknown; + try { + view = options.view === undefined ? undefined : JSON.parse(options.view); + } catch (cause) { + return err( + new AnalysisInputError(operation, { cause }, [ + { + path: ["view"], + reason: "invalid_format", + message: "view must be valid JSON", + }, + ]), + ); + } + const raw = { + detail: options.detail, + ...(view === undefined ? {} : { view }), + }; + const parsed = schema.safeParse(raw); + if (!parsed.success) + return err( + analysisInputErrorFromIssues(operation, parsed.error.issues, raw), + ); + const value = { ...parsed.data, evidenceOutput: options.evidence_output }; + const invalid = cliEvidenceDeliveryInputError({ ...value, operation }); + return invalid === undefined ? ok(value) : err(invalid); +}; + /** One-shot delivery owns report publication; analysis keeps its own cleanup owner. */ export const deliverCliAnalysisEvidence = async ( input: CliEvidenceDelivery & { diff --git a/src/cli/javascriptRouteDelivery.ts b/src/cli/javascriptRouteDelivery.ts index 88dc14e30c..71e254c27c 100644 --- a/src/cli/javascriptRouteDelivery.ts +++ b/src/cli/javascriptRouteDelivery.ts @@ -3,12 +3,9 @@ import { javaScriptRoutesDeliverySchema, webRoutesDeliverySchema, } from "../domain/analysisView/javascriptRoutesView.js"; -import { AnalysisInputError } from "../domain/analysisErrorCore.js"; -import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; -import { err, ok } from "../domain/result.js"; import { analysisEvidenceDeliveryOptions, - cliEvidenceDeliveryInputError, + parseCliAnalysisEvidenceDelivery, } from "./analysisEvidenceDelivery.js"; export const javascriptRouteDeliveryOptions = { @@ -30,34 +27,9 @@ export const parseJavaScriptRouteDelivery = ( view?: string | undefined; }, ) => { - let view: unknown; - try { - view = options.view === undefined ? undefined : JSON.parse(options.view); - } catch (cause) { - return err( - new AnalysisInputError(operation, { cause }, [ - { - path: ["view"], - reason: "invalid_format", - message: "view must be valid JSON", - }, - ]), - ); - } - const raw = { - detail: options.detail, - ...(view === undefined ? {} : { view }), - }; const schema = operation === "analyze_web_bundle" ? webRoutesDeliverySchema : javaScriptRoutesDeliverySchema; - const parsed = schema.safeParse(raw); - if (!parsed.success) - return err( - analysisInputErrorFromIssues(operation, parsed.error.issues, raw), - ); - const value = { ...parsed.data, evidenceOutput: options.evidence_output }; - const invalid = cliEvidenceDeliveryInputError({ ...value, operation }); - return invalid === undefined ? ok(value) : err(invalid); + return parseCliAnalysisEvidenceDelivery(operation, options, schema); }; diff --git a/src/cli/pythonPycCommands.ts b/src/cli/pythonPycCommands.ts index c58f083d2a..07fc2d11fd 100644 --- a/src/cli/pythonPycCommands.ts +++ b/src/cli/pythonPycCommands.ts @@ -8,7 +8,13 @@ import type { Logger } from "pino"; import type { CliInstance } from "./types.js"; import { withCommandCancellation } from "./commandCancellation.js"; import { inspectPythonPycInputSchema } from "../domain/python/pythonPyc.js"; -import { finishAnalysisCommand } from "./finishAnalysisCommand.js"; +import { + analysisEvidenceDeliveryOptions, + deliverCliAnalysisEvidence, + parseCliAnalysisEvidenceDelivery, +} from "./analysisEvidenceDelivery.js"; +import { pythonPycDeliverySchema } from "../domain/analysisView/pythonPycView.js"; +import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; /** Inspect operator-selected pyc files through the same shared workflow as MCP. */ export const registerPythonPycCommands = ( @@ -26,32 +32,47 @@ export const registerPythonPycCommands = ( maxObjects: inspectPythonPycInputSchema.shape.max_objects, maxDepth: inspectPythonPycInputSchema.shape.max_depth, maxStringBytes: inspectPythonPycInputSchema.shape.max_string_bytes, + ...analysisEvidenceDeliveryOptions, + view: z + .string() + .optional() + .describe( + "JSON python-pyc view; requires --detail summary and --evidence_output", + ), }), run: ({ args, options }) => - withCommandCancellation(async (signal) => { - const service = createPythonPycService(); - return await logCliCommand( - logger, - CLI_COMMANDS.inspectPythonPyc, - async () => - finishAnalysisCommand( - () => - service.inspect( - { - path: absoluteCallerPath(args.path), - max_file_bytes: options.maxFileBytes, - max_objects: options.maxObjects, - max_depth: options.maxDepth, - max_string_bytes: options.maxStringBytes, - }, - { signal }, - ), - service, - "rea-python-pyc", - "inspect_python_pyc", - signal, - ), - ); - }), + withCommandCancellation((signal) => + logCliCommand(logger, CLI_COMMANDS.inspectPythonPyc, async () => { + const delivery = parseCliAnalysisEvidenceDelivery( + "inspect_python_pyc", + options, + pythonPycDeliverySchema, + ); + if (!delivery.ok) return projectAnalysisError(delivery.error); + const service = createPythonPycService(); + return deliverCliAnalysisEvidence({ + run: () => + service.inspect( + { + path: absoluteCallerPath(args.path), + max_file_bytes: options.maxFileBytes, + max_objects: options.maxObjects, + max_depth: options.maxDepth, + max_string_bytes: options.maxStringBytes, + }, + { signal }, + ), + service, + providerId: "rea-python-pyc", + operation: "inspect_python_pyc", + signal, + detail: delivery.value.detail, + evidenceOutput: delivery.value.evidenceOutput, + ...(delivery.value.view === undefined + ? {} + : { view: delivery.value.view }), + }); + }), + ), }); }; diff --git a/src/contracts/analysisViewToolContracts.ts b/src/contracts/analysisViewToolContracts.ts index 744929bcab..84332a098b 100644 --- a/src/contracts/analysisViewToolContracts.ts +++ b/src/contracts/analysisViewToolContracts.ts @@ -17,7 +17,7 @@ export const ANALYSIS_VIEW_TOOL_CONTRACTS = [ ...toolContractMetadata("inspect_analysis_view"), kind: "application", description: - 'Use javascript-routes collections to query route patterns, source-linked occurrences, source status, unknowns or summary counts from retained JavaScript application or web-bundle Evidence. Inspect selected views of supported completed Evidence without repeating analysis. Choose a view compatible with the parent operation described below. Command summary views preserve ordered GDB records or opaque objdump stdout/stderr once, source parameters, completion facts and parent identity. They do not establish current target state. Historical capture summaries return identity and counts without bodies or archive inventories. Capture views page retained records: view {kind: "capture", detail: "preview" | "complete", offset: 0, limit: 20}; preview selects recorded headline fields and binary descriptors, complete returns exact records with all sidecars. Offsets follow the selected parent record order, preserving original producer ordinals. Paging never reopens the source or reruns the decoder. Native export tables use kind native-exports, collection exports/issues, exact names or original indices, selected fields and matching-row pagination; parent Evidence plus source_index identifies each row. Source coverage stays separate from page coverage. Go metadata uses kind go, collection dependencies/settings, exact paths/keys or indices, fields and pagination. Row index refers to the original collection; empty selections return no rows, and dependency coverage remains unknown. Rust metadata uses kind rust, collection packages/edges, exact names/versions or original indices, selected fields, edge from_indices/to_indices and matching-row pagination. Embedded-record coverage is separate from unknown runtime dependencies. For inspect_pcap_capture parents, kind pcap pages endpoints, dns-queries, tls-sni, protocols, http-uris or unattributed-addresses. Select an exact query for dns-addresses, or a tshark/capinfos stream for UTF-16 diagnostics text. Page totals count retained facts; source coverage remains explicit. Qlog summaries keep profile/scope and counts; kind qlog pages traces, streams, loss-declarations, incomplete-records, contributions, ranges or final-sizes. Except traces, select trace_index; stream-specific facets also require exact stream_id and direction. Facts retain all-recorded-events-before-selection scope, and interval overlap or endpoint loss declarations do not prove retransmission or network loss. The api-surface view groups retained HAR requests by method, origin and format-rule path template; templated segments are inferences. It reads no headers or bodies and reports no query values. The environment-manifest view projects the same HAR requests on the primary origin (most JSON-like records) into a replayable tool surface: view {kind: "environment-manifest", template_rules: ["after-uid-literal", "opaque-token", "sibling-variance"], include_query_parameter_names: true}. Each tool has a deterministic name, a path template and parameters from the four format rules plus the selected opt-in rules, observed query parameter names, a value-free body shape for POST/PUT/PATCH, status and content-type sets and record ordinals; records are pointers into the parent, excluded groups carry a reason and three fixed unknowns are always stated. Templates, parameters and names are inferences; the view copies no header, cookie or body values and refuses parents inspected with sensitive values. A capture_browser_scenario parent that selected response bodies or header values projects the same way, one record per network transaction in request order; it relies on capture-time redaction of declared secrets instead of that refusal, and a transaction whose URL was redacted is left unrepresented. The environment-state view extracts, from the same parents, the initial state the recorded reads observed: view {kind: "environment-state", template_rules: [...]} with the rules of the manifest it accompanies. It reads response bodies recorded before the first related write and reports entity types with an identity field (proved by a path parameter or inferred from a field name), entities in collection order with per-view values where views differ, documents for non-parameterized reads that are not entities and have one initial request line (item-shaped paths stay recorded), and a render plan per read tool: item and list views reproduce their latest recordings from the state (a list also learns the filters, search, sort and paging its recordings verify, and answers new values of them from the state), and every other read tool is a recorded view with a reason. It also reports a write plan per state-changing tool (create, update, delete or document, built from its first successful call) and write_clock; a plan is served only when replaying the capture shows a later read confirming its effect and none contradicting it. Types and identity are inferences; values are recorded data, so it refuses parents inspected with sensitive values, and a scenario parent must have selected response bodies; its values keep the capture redaction markers, and a write whose URL it redacted ends the state of every type. For a retained record use source: {kind: "retained-evidence", evidence_id: ""}. Use normalized_result.parent_evidence_id from a summary receipt. Source kind is required. Portable source uses {kind: "inline", evidence: }. A completed Evidence JSON file on this host uses {kind: "evidence-file", path: ""}: REA reads and authenticates it, then projects it like retained Evidence, so a large capture is bounded by the file reader rather than by inline-input accounting. The file may also be an Evidence bundle written by CLI --evidence_output or export_evidence_bundle; select evidence_id when it contains more than one record. For layout, JavaScript, inventory, WASM, Mach-O, Interface Builder, EVM, managed artifact, Android package and Dart AOT sources, request a summary with view: {kind: "summary"}; summary is a view kind and has no facet. The WASM summary gives validation, counts, WAT digest/length, tool profile and execution/network states without reading WAT text. Select a layout mitigations/linkage facet, one section/symbol/module, or a stable page. Module pages include assets, bundled and source modules; use node_id for ambiguous or unavailable paths. Typed module references use view {kind: "javascript", collection: "modules", projection: "references", where: {path: "main.js", kind: "javascript-module"}, offset: 0, limit: 64}; where matches exact retained strings (null selects unavailable paths). Rows include canonical identity, retained source-range count and observation support states. Source coverage and selection remain explicit; exhausting matching rows does not prove runtime loading or complete analysis. For exact file/native facts and processing gaps, use view {kind: "javascript-facts", where: {paths: ["package.json"]}, property_keys: ["name", "operation", "analysis_status"]}. Rows page original observations with canonical identities and Evidence; missing properties remain omitted. Paths match recorded artifact-path/source-range locations; optional kinds filter nodes. To discover field names without values, opt into include_available_property_keys: true with property_keys: []. Canonical identity and Evidence are separate from properties; keep each node_id/observation_id pair together when composing results. Inventory uses view: {kind: "inventory", collection: "occurrences", logical_paths: ["package.json"], offset: 0, limit: 64}. Inventory views page nodes, occurrences, edges or integrity contradictions. Occurrences accept exact logical_paths and occurrence_ids; nodes accept artifact_ids. Batch filters intersect (omitted means all available, [] means none); fields selects columns while retaining identity links. source_complete describes retained graph membership; source_coverage describes producer coverage, including unknown encrypted PCK directories. Page exhaustion does not prove absence in a partial source. WASM views use kind wasm and facet sections/imports/exports/candidates/glue/wat/headers/details/tool_profile; text facets use UTF-16 code units and source forms remain exact. Mach-O views use kind macho and facet entrypoints/architectures/build_metadata/load_commands/dependencies/imports/exports/segments/provenance; source_exhaustive false means unreturned rows may exist. Interface Builder views use kind interface-builder and collection documents/nodes/edges/coverage; nodes and edges accept an exact document relative_path. EVM summary returns all inferred interface candidates, carrier/decoded identities and explicit unknowns without hex or duplicate stdout. EVM views use kind evm and facet functions/bytecode_hex/stdout/stderr; function pages preserve the original candidate rows, and text offsets/limits count UTF-16 code units (hex has two characters per decoded byte). Exhausting candidate pages does not establish global interface completeness. Managed artifact summaries preserve complete identity/classification, metadata status, module/MVID, frameworks, coverage and limitations without unrelated inventories. Managed pages use kind managed and collection references/resources/attributes, retaining source coverage and build-local token scope; page exhaustion does not establish complete metadata. Managed member summaries keep identity, selection with unmatched names, coverage and per-collection counts; managed-members pages use collection types/exported_types/fields/methods/member_refs/call_edges/field_accesses/user_strings and carry the source selection and coverage. Android package summaries retain all declarations, engine identity and counts without decoded manifest text. Android manifest pages use kind android, facet manifest, offset and limit in UTF-16 code units; source_status preserves partial decoded coverage even when the retained text page is exhausted. Dart AOT summary preserves target/ABI/library identity, snapshot headers and pool lower bounds. String pages use kind dart-aot, collection package_uris/dart_uris/class_like_tokens; source coverage remains partial when the producer says partial. Protobuf views use kind protobuf, collection bodies/frames/fields, with record_ordinals, body_pointers and fields; field pages also select frame_indices, field_ids, field_numbers or schema_names. Byte identity, offset basis and body status remain present. Text and schema_value are opt-in page columns. APK signing views use kind apk-signature and collection signers/digests/lineage, with ids, signer_ids, schemes, fields and paging. Identity links remain when fields is empty. Declared identities and digest matches do not establish signature verification or authenticity. Model views use kind model-artifact and collection metadata (keys), tensors (names/types) or types (derived totals), with indices, fields and paging. Header SHA-256 scopes declarations; full-file identity remains unknown unless full hashing was selected. Arrays retain typed eight-element previews with explicit omissions. IL2CPP metadata views use kind il2cpp-metadata and collection sections/images/types/methods/fields/properties/events/parameters/string_literals/code_gen_modules/method_addresses/return_types/field_types/parameter_types. Select indices, fields and pages; sections/images/types/methods/fields/properties/events/parameters accept names, types also accept image_indices/namespaces/declaring_types, and methods/members accept declaring_types. Parameters and parameter_types accept method_indices; code_gen_modules accept image_indices; method_addresses accept addresses/states, preserving every folded alternative. Row index is scoped to the metadata digest; native pages add binary_source. Uncollected native tables have null counts and remain unavailable. Desktop directory views use kind desktop-application and collection files/omissions/candidates/components/next_probes, with fields and pagination; files and omissions accept exact paths or their stable IDs, and candidates accept families. Source coverage and stable row IDs remain available. Native inventory Evidence from list_names, list/search_procedures or list/search_strings uses view: {kind: "native-inventory", offset: 0, limit: 64, fields: ["value"]}. Rows keep source_index and address even with selected fields. Summary counts describe retained rows; provider discovery coverage remains unknown. Native function Evidence accepts kind summary for identity, availability and facet counts, or kind native for selected facts: view: {kind: "native", facet: "procedure"}. Facets select procedure, pseudocode, assembly, basic blocks, comments, callers/callees, references, unresolved calls, referenced strings/names, native API and high-pcode. Use nonnegative offset and a positive caller-selected limit (defaults 0 and 64); pseudocode uses UTF-16 code units, other native pages use rows. Returns artifact identity, parent Evidence ID, a distinct view digest, coverage, limitations and unknowns. Actual serialized size determines MCP transport admission; choose a smaller page or export the complete retained Evidence if needed.', + 'Use javascript-routes collections to query route patterns, source-linked occurrences, source status, unknowns or summary counts from retained JavaScript application or web-bundle Evidence. Inspect selected views of supported completed Evidence without repeating analysis. Choose a view compatible with the parent operation described below. Command summary views preserve ordered GDB records or opaque objdump stdout/stderr once, source parameters, completion facts and parent identity. They do not establish current target state. Historical capture summaries return identity and counts without bodies or archive inventories. Capture views page retained records: view {kind: "capture", detail: "preview" | "complete", offset: 0, limit: 20}; preview selects recorded headline fields and binary descriptors, complete returns exact records with all sidecars. Offsets follow the selected parent record order, preserving original producer ordinals. Paging never reopens the source or reruns the decoder. Native export tables use kind native-exports, collection exports/issues, exact names or original indices, selected fields and matching-row pagination; parent Evidence plus source_index identifies each row. Source coverage stays separate from page coverage. PYC views use kind python-pyc, collection code_objects, names/indices/fields and paging; source_index scopes every same-name alternative to the parent, nested_code keeps code-constant references, and pyc.coverage preserves source coverage. Go metadata uses kind go, collection dependencies/settings, exact paths/keys or indices, fields and pagination. Row index refers to the original collection; empty selections return no rows, and dependency coverage remains unknown. Rust metadata uses kind rust, collection packages/edges, exact names/versions or original indices, selected fields, edge from_indices/to_indices and matching-row pagination. Embedded-record coverage is separate from unknown runtime dependencies. For inspect_pcap_capture parents, kind pcap pages endpoints, dns-queries, tls-sni, protocols, http-uris or unattributed-addresses. Select an exact query for dns-addresses, or a tshark/capinfos stream for UTF-16 diagnostics text. Page totals count retained facts; source coverage remains explicit. Qlog summaries keep profile/scope and counts; kind qlog pages traces, streams, loss-declarations, incomplete-records, contributions, ranges or final-sizes. Except traces, select trace_index; stream-specific facets also require exact stream_id and direction. Facts retain all-recorded-events-before-selection scope, and interval overlap or endpoint loss declarations do not prove retransmission or network loss. The api-surface view groups retained HAR requests by method, origin and format-rule path template; templated segments are inferences. It reads no headers or bodies and reports no query values. The environment-manifest view projects the same HAR requests on the primary origin (most JSON-like records) into a replayable tool surface: view {kind: "environment-manifest", template_rules: ["after-uid-literal", "opaque-token", "sibling-variance"], include_query_parameter_names: true}. Each tool has a deterministic name, a path template and parameters from the four format rules plus the selected opt-in rules, observed query parameter names, a value-free body shape for POST/PUT/PATCH, status and content-type sets and record ordinals; records are pointers into the parent, excluded groups carry a reason and three fixed unknowns are always stated. Templates, parameters and names are inferences; the view copies no header, cookie or body values and refuses parents inspected with sensitive values. A capture_browser_scenario parent that selected response bodies or header values projects the same way, one record per network transaction in request order; it relies on capture-time redaction of declared secrets instead of that refusal, and a transaction whose URL was redacted is left unrepresented. The environment-state view extracts, from the same parents, the initial state the recorded reads observed: view {kind: "environment-state", template_rules: [...]} with the rules of the manifest it accompanies. It reads response bodies recorded before the first related write and reports entity types with an identity field (proved by a path parameter or inferred from a field name), entities in collection order with per-view values where views differ, documents for non-parameterized reads that are not entities and have one initial request line (item-shaped paths stay recorded), and a render plan per read tool: item and list views reproduce their latest recordings from the state (a list also learns the filters, search, sort and paging its recordings verify, and answers new values of them from the state), and every other read tool is a recorded view with a reason. It also reports a write plan per state-changing tool (create, update, delete or document, built from its first successful call) and write_clock; a plan is served only when replaying the capture shows a later read confirming its effect and none contradicting it. Types and identity are inferences; values are recorded data, so it refuses parents inspected with sensitive values, and a scenario parent must have selected response bodies; its values keep the capture redaction markers, and a write whose URL it redacted ends the state of every type. For a retained record use source: {kind: "retained-evidence", evidence_id: ""}. Use normalized_result.parent_evidence_id from a summary receipt. Source kind is required. Portable source uses {kind: "inline", evidence: }. A completed Evidence JSON file on this host uses {kind: "evidence-file", path: ""}: REA reads and authenticates it, then projects it like retained Evidence, so a large capture is bounded by the file reader rather than by inline-input accounting. The file may also be an Evidence bundle written by CLI --evidence_output or export_evidence_bundle; select evidence_id when it contains more than one record. For layout, JavaScript, inventory, WASM, Mach-O, Interface Builder, EVM, managed artifact, Android package and Dart AOT sources, request a summary with view: {kind: "summary"}; summary is a view kind and has no facet. The WASM summary gives validation, counts, WAT digest/length, tool profile and execution/network states without reading WAT text. Select a layout mitigations/linkage facet, one section/symbol/module, or a stable page. Module pages include assets, bundled and source modules; use node_id for ambiguous or unavailable paths. Typed module references use view {kind: "javascript", collection: "modules", projection: "references", where: {path: "main.js", kind: "javascript-module"}, offset: 0, limit: 64}; where matches exact retained strings (null selects unavailable paths). Rows include canonical identity, retained source-range count and observation support states. Source coverage and selection remain explicit; exhausting matching rows does not prove runtime loading or complete analysis. For exact file/native facts and processing gaps, use view {kind: "javascript-facts", where: {paths: ["package.json"]}, property_keys: ["name", "operation", "analysis_status"]}. Rows page original observations with canonical identities and Evidence; missing properties remain omitted. Paths match recorded artifact-path/source-range locations; optional kinds filter nodes. To discover field names without values, opt into include_available_property_keys: true with property_keys: []. Canonical identity and Evidence are separate from properties; keep each node_id/observation_id pair together when composing results. Inventory uses view: {kind: "inventory", collection: "occurrences", logical_paths: ["package.json"], offset: 0, limit: 64}. Inventory views page nodes, occurrences, edges or integrity contradictions. Occurrences accept exact logical_paths and occurrence_ids; nodes accept artifact_ids. Batch filters intersect (omitted means all available, [] means none); fields selects columns while retaining identity links. source_complete describes retained graph membership; source_coverage describes producer coverage, including unknown encrypted PCK directories. Page exhaustion does not prove absence in a partial source. WASM views use kind wasm and facet sections/imports/exports/candidates/glue/wat/headers/details/tool_profile; text facets use UTF-16 code units and source forms remain exact. Mach-O views use kind macho and facet entrypoints/architectures/build_metadata/load_commands/dependencies/imports/exports/segments/provenance; source_exhaustive false means unreturned rows may exist. Interface Builder views use kind interface-builder and collection documents/nodes/edges/coverage; nodes and edges accept an exact document relative_path. EVM summary returns all inferred interface candidates, carrier/decoded identities and explicit unknowns without hex or duplicate stdout. EVM views use kind evm and facet functions/bytecode_hex/stdout/stderr; function pages preserve the original candidate rows, and text offsets/limits count UTF-16 code units (hex has two characters per decoded byte). Exhausting candidate pages does not establish global interface completeness. Managed artifact summaries preserve complete identity/classification, metadata status, module/MVID, frameworks, coverage and limitations without unrelated inventories. Managed pages use kind managed and collection references/resources/attributes, retaining source coverage and build-local token scope; page exhaustion does not establish complete metadata. Managed member summaries keep identity, selection with unmatched names, coverage and per-collection counts; managed-members pages use collection types/exported_types/fields/methods/member_refs/call_edges/field_accesses/user_strings and carry the source selection and coverage. Android package summaries retain all declarations, engine identity and counts without decoded manifest text. Android manifest pages use kind android, facet manifest, offset and limit in UTF-16 code units; source_status preserves partial decoded coverage even when the retained text page is exhausted. Dart AOT summary preserves target/ABI/library identity, snapshot headers and pool lower bounds. String pages use kind dart-aot, collection package_uris/dart_uris/class_like_tokens; source coverage remains partial when the producer says partial. Protobuf views use kind protobuf, collection bodies/frames/fields, with record_ordinals, body_pointers and fields; field pages also select frame_indices, field_ids, field_numbers or schema_names. Byte identity, offset basis and body status remain present. Text and schema_value are opt-in page columns. APK signing views use kind apk-signature and collection signers/digests/lineage, with ids, signer_ids, schemes, fields and paging. Identity links remain when fields is empty. Declared identities and digest matches do not establish signature verification or authenticity. Model views use kind model-artifact and collection metadata (keys), tensors (names/types) or types (derived totals), with indices, fields and paging. Header SHA-256 scopes declarations; full-file identity remains unknown unless full hashing was selected. Arrays retain typed eight-element previews with explicit omissions. IL2CPP metadata views use kind il2cpp-metadata and collection sections/images/types/methods/fields/properties/events/parameters/string_literals/code_gen_modules/method_addresses/return_types/field_types/parameter_types. Select indices, fields and pages; sections/images/types/methods/fields/properties/events/parameters accept names, types also accept image_indices/namespaces/declaring_types, and methods/members accept declaring_types. Parameters and parameter_types accept method_indices; code_gen_modules accept image_indices; method_addresses accept addresses/states, preserving every folded alternative. Row index is scoped to the metadata digest; native pages add binary_source. Uncollected native tables have null counts and remain unavailable. Desktop directory views use kind desktop-application and collection files/omissions/candidates/components/next_probes, with fields and pagination; files and omissions accept exact paths or their stable IDs, and candidates accept families. Source coverage and stable row IDs remain available. Native inventory Evidence from list_names, list/search_procedures or list/search_strings uses view: {kind: "native-inventory", offset: 0, limit: 64, fields: ["value"]}. Rows keep source_index and address even with selected fields. Summary counts describe retained rows; provider discovery coverage remains unknown. Native function Evidence accepts kind summary for identity, availability and facet counts, or kind native for selected facts: view: {kind: "native", facet: "procedure"}. Facets select procedure, pseudocode, assembly, basic blocks, comments, callers/callees, references, unresolved calls, referenced strings/names, native API and high-pcode. Use nonnegative offset and a positive caller-selected limit (defaults 0 and 64); pseudocode uses UTF-16 code units, other native pages use rows. Returns artifact identity, parent Evidence ID, a distinct view digest, coverage, limitations and unknowns. Actual serialized size determines MCP transport admission; choose a smaller page or export the complete retained Evidence if needed.', inputSchema: inspectAnalysisViewInputSchema, outputSchema: evidenceResultOf(analysisViewResultSchema), examples: [ diff --git a/src/contracts/python/pythonPycToolContracts.ts b/src/contracts/python/pythonPycToolContracts.ts index c4a4d758f2..858f7db18e 100644 --- a/src/contracts/python/pythonPycToolContracts.ts +++ b/src/contracts/python/pythonPycToolContracts.ts @@ -4,7 +4,24 @@ import { } from "../../domain/python/pythonPyc.js"; import type { ToolContract } from "../toolContractTypes.js"; import { toolContractMetadata } from "../toolEffects.js"; -import { evidenceResultOf } from "../toolOutputSchemaPrimitives.js"; +import { analysisDetailOutputSchemaOf } from "../analysisDetail.js"; +import { z } from "zod"; +import { + pythonPycSummaryViewResultSchema, + pythonPycCodeObjectsViewResultSchema, +} from "../../domain/analysisView/analysisView.js"; +import { + pythonPycCompleteDeliveryShape, + pythonPycSummaryDeliveryShape, +} from "../../domain/analysisView/pythonPycView.js"; + +/** Delivery selection does not change provider inputs or complete Evidence identity. */ +export const inspectPythonPycRequestSchema = z + .discriminatedUnion("detail", [ + inspectPythonPycInputSchema.extend(pythonPycCompleteDeliveryShape), + inspectPythonPycInputSchema.extend(pythonPycSummaryDeliveryShape), + ]) + .meta({ "x-rea-root-discriminator": "detail" }); /** Static CPython pyc inspection is independent of active native-analysis providers. */ export const PYTHON_PYC_TOOL_CONTRACTS = [ @@ -14,14 +31,39 @@ export const PYTHON_PYC_TOOL_CONTRACTS = [ kind: "artifact-provider", requiresActiveTarget: true, description: - "Inspect one explicit local CPython 3.7–3.13 .pyc file with a supported 16-byte header without executing bytecode, invoking CPython or recovering source. Returns the header (magic, pinned python_version, PEP 552 flags, source hash or mtime/size), the top-level code object with nested functions, names, const summaries, and bytecode length/SHA-256. Marshal is walked with object, depth and string budgets; unsupported magics/layouts, truncated streams and exhausted budgets return no partial success. A decoded header is not proof the matching .py is present. Portable on supported Node hosts; no Python installation required.", - inputSchema: inspectPythonPycInputSchema, - outputSchema: evidenceResultOf(pythonPycSchema), + "Inspect one explicit local CPython 3.7–3.13 .pyc file with a supported 16-byte header without executing bytecode, invoking CPython or recovering source. Returns the header (magic, pinned python_version, PEP 552 flags, source hash or mtime/size), the top-level code object with nested functions, names, const summaries, and bytecode length/SHA-256. Complete output is the default. detail summary retains the complete Evidence and returns header/coverage. Include an optional python-pyc code_objects view to return selected names/indices/fields in the first call; inspect_analysis_view pages the same retained source later. Every matching display name survives under its parent-scoped source_index; nested_code represents code-constant references, not runtime calls. Marshal is walked with object, depth and string budgets; unsupported magics/layouts, truncated streams and exhausted budgets return no partial success. A decoded header is not proof the matching .py is present. Portable on supported Node hosts; no Python installation required.", + inputSchema: inspectPythonPycRequestSchema, + outputSchema: analysisDetailOutputSchemaOf( + pythonPycSchema, + z.union([ + pythonPycSummaryViewResultSchema, + pythonPycCodeObjectsViewResultSchema, + ]), + ), examples: [ + { + title: + "Return selected function facts immediately and retain the complete report", + input: { + path: "/artifacts/module.pyc", + detail: "summary", + view: { + kind: "python-pyc", + collection: "code_objects", + names: ["parse_args"], + fields: ["name", "qualname", "bytecode", "nested_code"], + }, + }, + }, { title: "Inspect a CPython 3.12 pyc header and nested code objects", input: { path: "/artifacts/module.pyc" }, }, + { + title: + "Retain the complete pyc report and return only its header and coverage", + input: { path: "/artifacts/module.pyc", detail: "summary" }, + }, ], }, ] as const satisfies readonly ToolContract[]; diff --git a/src/contracts/pythonPycDeliveryContracts.test.ts b/src/contracts/pythonPycDeliveryContracts.test.ts new file mode 100644 index 0000000000..3d8074de97 --- /dev/null +++ b/src/contracts/pythonPycDeliveryContracts.test.ts @@ -0,0 +1,58 @@ +import { Ajv2020 } from "ajv/dist/2020.js"; +import { Ajv } from "ajv"; +import { expect, it } from "vitest"; +import { toolContract } from "./toolContracts.js"; +import { toolInputSchemaWithMetadata } from "./toolSchemaMetadata.js"; + +const view = { + kind: "python-pyc", + collection: "code_objects", + fields: ["name"], +}; + +it.each(["draft-2020-12", "draft-07"] as const)( + "advertises pyc delivery combinations in %s", + (target) => { + const contract = toolContract("inspect_python_pyc"); + const projected = toolInputSchemaWithMetadata(contract)[ + "~standard" + ].jsonSchema.input({ target }); + const ajv = + target === "draft-2020-12" + ? new Ajv2020({ strict: false, validateFormats: false }) + : new Ajv({ strict: false, validateFormats: false }); + expect(ajv.validateSchema(projected)).toBe(true); + expect(projected).toMatchObject({ type: "object" }); + for (const key of ["anyOf", "oneOf", "allOf", "x-rea-root-discriminator"]) + expect(projected).not.toHaveProperty(key); + const validate = ajv.compile(projected); + for (const [delivery, valid] of [ + [{}, true], + [{ detail: "complete" }, true], + [{ detail: "summary" }, true], + [{ detail: "summary", view }, true], + [ + { + detail: "summary", + view: { ...view, names: [], fields: [], indices: [] }, + }, + true, + ], + [{ view }, false], + [{ detail: "complete", view }, false], + [{ detail: "summary", view: { ...view, fields: ["missing"] } }, false], + [{ detail: "summary", view: { ...view, kind: "native-exports" } }, false], + [{ detail: "summary", view, unexpected: true }, false], + ] as const) { + const input = { path: "/tmp/module.pyc", ...delivery }; + expect( + contract.inputSchema.safeParse(input).success, + JSON.stringify(input), + ).toBe(valid); + expect( + validate(input), + JSON.stringify({ input, errors: validate.errors }), + ).toBe(valid); + } + }, +); diff --git a/src/domain/analysisView/analysisView.ts b/src/domain/analysisView/analysisView.ts index 6e945091fb..a04107cbd1 100644 --- a/src/domain/analysisView/analysisView.ts +++ b/src/domain/analysisView/analysisView.ts @@ -8,6 +8,12 @@ import { apkSignatureSourceCoverageSchema, } from "./apkSignatureView.js"; import { byteEntropySchema } from "../native/byteEntropy.js"; +import { + pythonPycViewRequestSchema, + pythonPycViewItemsSchema, + pythonPycSummarySchema, + projectPythonPycView, +} from "./pythonPycView.js"; import { nativeExportsViewRequestSchema, nativeExportsViewItemsSchema, @@ -334,6 +340,7 @@ const nativeViewSchema = z.strictObject({ /** Caller-selected projection of already completed analysis Evidence. */ export const analysisViewRequestSchema = z.discriminatedUnion("kind", [ + pythonPycViewRequestSchema, nativeExportsViewRequestSchema, javaScriptRoutesViewRequestSchema, nativeFunctionMatchingViewRequestSchema, @@ -457,6 +464,7 @@ const javascriptSummarySchema = z.strictObject({ const viewResultBase = { parent_evidence_id: prefixedDigestSchema("ev"), parent_operation: z.enum([ + "inspect_python_pyc", "inspect_byte_entropy", "inspect_native_exports", "inspect_binary_layout", @@ -512,6 +520,21 @@ export const nativeExportsSummaryViewResultSchema = z.strictObject({ summary: nativeExportsSummarySchema, }); +export const pythonPycSummaryViewResultSchema = z.strictObject({ + ...summaryViewResultBase, + parent_operation: z.literal("inspect_python_pyc"), + summary: pythonPycSummarySchema, +}); + +export const pythonPycCodeObjectsViewResultSchema = z.strictObject({ + ...viewResultBase, + kind: z.literal("python-pyc"), + parent_operation: z.literal("inspect_python_pyc"), + view: pythonPycViewRequestSchema, + pyc: pythonPycSummarySchema, + items: pythonPycViewItemsSchema, +}); + /** Opt-in command delivery keeps output once and retains the complete parent. */ export const gdbCommandSummaryViewResultSchema = z.strictObject({ ...summaryViewResultBase, @@ -922,6 +945,7 @@ export const analysisViewResultSchema = z.discriminatedUnion("kind", [ view: environmentStateViewRequestSchema, }), z.discriminatedUnion("parent_operation", [ + pythonPycSummaryViewResultSchema, nativeExportsSummaryViewResultSchema, byteEntropySummaryViewResultSchema, gdbCommandSummaryViewResultSchema, @@ -952,6 +976,7 @@ export const analysisViewResultSchema = z.discriminatedUnion("kind", [ nativeFunctionSummaryViewResultSchema, sourceToBundleSummaryViewResultSchema, ]), + pythonPycCodeObjectsViewResultSchema, z.strictObject({ ...viewResultBase, kind: z.literal("native-exports"), @@ -1219,6 +1244,8 @@ const parentArtifactPath = (parent: AnalysisViewParent): string => { /** Named source owners for views that need no extra decoder or accumulator. */ const retainedViewProjectors = new Map([ + ["inspect_native_exports", projectNativeExportsView], + ["inspect_python_pyc", projectPythonPycView], ["frida_session_status", projectFridaSessionStatusView], ["gdb_console", projectCommandObservationView], ["inspect_with_objdump", projectCommandObservationView], @@ -1315,10 +1342,6 @@ export const projectAnalysisView = ( const projected = projectPcapCaptureView(parent, view); return projected.ok ? ok(sealAnalysisView(projected.value)) : projected; } - if (parent.operation === "inspect_native_exports") { - const projected = projectNativeExportsView(parent, view); - return projected.ok ? ok(sealAnalysisView(projected.value)) : projected; - } if (parent.operation === "inspect_byte_entropy") { const report = parseAnalysisViewResult(byteEntropySchema, parent); if (!report.ok) return report; diff --git a/src/domain/analysisView/analysisViewParent.test.ts b/src/domain/analysisView/analysisViewParent.test.ts new file mode 100644 index 0000000000..86b95ff10b --- /dev/null +++ b/src/domain/analysisView/analysisViewParent.test.ts @@ -0,0 +1,52 @@ +import { expect, it } from "vitest"; +import { z } from "zod"; +import { + freezeOwnedJsonTreeSnapshot, + isImmutableJsonSnapshot, +} from "../immutableJson.js"; +import type { AnalysisViewParent } from "./analysisView.js"; +import { createAnalysisViewResultReader } from "./analysisViewParent.js"; + +const parent = (normalizedResult: unknown): AnalysisViewParent => ({ + evidenceId: `ev_${"a".repeat(64)}`, + operation: "inspect_python_pyc", + normalizedResult, + sealedResult: true, + limitations: [], +}); + +it("does not trust a caller's sealed flag or shallow freeze for mutable facts", () => { + const item = { value: "valid" }; + const source = Object.freeze({ items: [item] }); + const read = createAnalysisViewResultReader( + z.strictObject({ + items: z.array(z.strictObject({ value: z.literal("valid") })), + }), + ); + expect(read(parent(source)).ok).toBe(true); + item.value = "changed"; + expect(read(parent(source)).ok).toBe(false); +}); + +it("keeps schema-specific normalized facts separate without mutating sealed source", () => { + const source = freezeOwnedJsonTreeSnapshot({}); + const first = createAnalysisViewResultReader( + z.strictObject({ + value: z.string().default("first"), + }), + ); + const second = createAnalysisViewResultReader( + z.strictObject({ + value: z.string().default("second"), + }), + ); + const a = first(parent(source)); + const b = second(parent(source)); + expect(a).toEqual({ ok: true, value: { value: "first" } }); + expect(b).toEqual({ ok: true, value: { value: "second" } }); + expect(source).toEqual({}); + if (a.ok && b.ok) { + expect(isImmutableJsonSnapshot(a.value)).toBe(true); + expect(isImmutableJsonSnapshot(b.value)).toBe(true); + } +}); diff --git a/src/domain/analysisView/analysisViewParent.ts b/src/domain/analysisView/analysisViewParent.ts index 27192d054e..4a20bc8f3a 100644 --- a/src/domain/analysisView/analysisViewParent.ts +++ b/src/domain/analysisView/analysisViewParent.ts @@ -4,6 +4,38 @@ import type { AnalysisError } from "../analysisErrorBase.js"; import { analysisInputErrorFromIssues } from "../inputIssueProjection.js"; import { err, ok, type Result } from "../result.js"; import type { AnalysisViewParent } from "./analysisView.js"; +import type { JsonValue } from "../jsonValue.js"; +import { + canBorrowParsedJsonSnapshot, + freezeOwnedJsonTreeSnapshot, + isImmutableJsonSnapshot, +} from "../immutableJson.js"; + +/** One owning schema reuses validated immutable facts for their retained lifetime. */ +export const createAnalysisViewResultReader = < + Value extends JsonValue, +>(parser: { + readonly safeParse: (input: unknown) => z.ZodSafeParseResult; +}): ((parent: AnalysisViewParent) => Result) => { + const checked = new WeakMap(); + return (parent) => { + const source = parent.normalizedResult; + const reusable = + parent.sealedResult === true && + typeof source === "object" && + source !== null && + isImmutableJsonSnapshot(source); + const known = reusable ? checked.get(source) : undefined; + if (known !== undefined) return ok(known); + const parsed = parseAnalysisViewResult(parser, parent); + if (!parsed.ok || !reusable) return parsed; + const report = canBorrowParsedJsonSnapshot(source, parsed.value) + ? source + : freezeOwnedJsonTreeSnapshot(parsed.value); + checked.set(source, report); + return ok(report); + }; +}; /** Parse a parent's result with its owning parser; issues point into the view source. */ export const parseAnalysisViewResult = ( diff --git a/src/domain/analysisView/nativeExportsView.ts b/src/domain/analysisView/nativeExportsView.ts index 97dd500528..579ef62c7f 100644 --- a/src/domain/analysisView/nativeExportsView.ts +++ b/src/domain/analysisView/nativeExportsView.ts @@ -5,11 +5,6 @@ import { elfNativeExportsSchema as elf, type NativeExports, } from "../native/nativeExports.js"; -import { - canBorrowParsedJsonSnapshot, - freezeOwnedJsonTreeSnapshot, - isImmutableJsonSnapshot, -} from "../immutableJson.js"; import type { AnalysisError } from "../analysisErrorBase.js"; import { err, ok, type Result } from "../result.js"; import type { @@ -18,7 +13,7 @@ import type { UnsignedAnalysisView, } from "./analysisView.js"; import { analysisViewInputError } from "./analysisViewErrors.js"; -import { parseAnalysisViewResult } from "./analysisViewParent.js"; +import { createAnalysisViewResultReader } from "./analysisViewParent.js"; import { boundedViewMessages, completeWithinViewCoverage, @@ -83,31 +78,7 @@ export const nativeExportsSummarySchema = z.discriminatedUnion("format", [ elf.omit({ exports: true, issues: true, limitations: true }).extend(counts), ]); -// This validates exact immutable Evidence, never a path or current filesystem state. -// Weak keys release their parsed representation with the owning retained record. -const checked = new WeakMap(); -const readReport = ( - parent: AnalysisViewParent, -): Result => { - const source = parent.normalizedResult; - const reusable = - parent.sealedResult === true && - typeof source === "object" && - source !== null && - isImmutableJsonSnapshot(source); - const known = reusable ? checked.get(source) : undefined; - if (known !== undefined) return ok(known); - const parsed = parseAnalysisViewResult(nativeExportsSchema, parent); - if (!parsed.ok) return parsed; - if (reusable) { - const report = canBorrowParsedJsonSnapshot(source, parsed.value) - ? source - : freezeOwnedJsonTreeSnapshot(parsed.value); - checked.set(source, report); - return ok(report); - } - return parsed; -}; +const readReport = createAnalysisViewResultReader(nativeExportsSchema); /** Read selected export facts without reopening the artifact or reinterpreting its tables. */ export const projectNativeExportsView = ( diff --git a/src/domain/analysisView/pythonPycView.ts b/src/domain/analysisView/pythonPycView.ts new file mode 100644 index 0000000000..1cd0381c08 --- /dev/null +++ b/src/domain/analysisView/pythonPycView.ts @@ -0,0 +1,145 @@ +import { z } from "zod"; +import { + pythonPycSchema, + type PythonPycCodeObject, +} from "../python/pythonPyc.js"; +import type { AnalysisError } from "../analysisErrorBase.js"; +import { err, ok, type Result } from "../result.js"; +import type { + AnalysisViewParent, + AnalysisViewRequest, + UnsignedAnalysisView, +} from "./analysisView.js"; +import { analysisViewInputError } from "./analysisViewErrors.js"; +import { createAnalysisViewResultReader } from "./analysisViewParent.js"; +import { + boundedViewMessages, + completeWithinViewCoverage, + pageViewCoverage, +} from "./analysisViewCoverage.js"; +import { selectViewRows } from "./selectViewRows.js"; + +const code = pythonPycSchema.shape.code_objects.element; + +/** Names match retained display strings; every matching source index survives. */ +export const pythonPycViewRequestSchema = z.strictObject({ + kind: z.literal("python-pyc"), + collection: z.literal("code_objects"), + names: z.array(z.string()).optional(), + indices: z.array(z.int().nonnegative()).optional(), + fields: z.array(code.keyof()).optional(), + offset: z.int().nonnegative().default(0), + limit: z.int().positive().default(64), +}); + +export const pythonPycCompleteDeliveryShape = { + detail: z.literal("complete").optional().default("complete"), + view: z.never().exactOptional(), +}; +export const pythonPycSummaryDeliveryShape = { + detail: z.literal("summary"), + view: pythonPycViewRequestSchema.exactOptional(), +}; +export const pythonPycDeliverySchema = z.discriminatedUnion("detail", [ + z.strictObject(pythonPycCompleteDeliveryShape), + z.strictObject(pythonPycSummaryDeliveryShape), +]); + +export const pythonPycViewItemsSchema = z.array( + code.partial().extend({ source_index: z.int().nonnegative() }), +); + +/** Source coverage and constructor/header qualifications stay separate from paging. */ +export const pythonPycSummarySchema = z.strictObject({ + artifact: pythonPycSchema.shape.artifact, + header: pythonPycSchema.shape.header, + coverage: pythonPycSchema.shape.coverage, + limitation_count: z.int().nonnegative(), +}); + +const readReport = createAnalysisViewResultReader(pythonPycSchema); + +/** Project code-constant references from historical facts, without reopening the pyc. */ +export const projectPythonPycView = ( + parent: AnalysisViewParent, + view: AnalysisViewRequest, +): Result => { + if (view.kind !== "summary" && view.kind !== "python-pyc") + return err( + analysisViewInputError([ + { + path: ["view"], + reason: "invalid_value", + message: "Python pyc Evidence supports summary and python-pyc views.", + }, + ]), + ); + const parsed = readReport(parent); + if (!parsed.ok) return parsed; + const { code_objects, limitations, ...identity } = parsed.value; + const summary = { ...identity, limitation_count: limitations.length }; + const shared = { + parent_evidence_id: parent.evidenceId, + parent_operation: "inspect_python_pyc" as const, + parent_digest: parent.evidenceId.slice(3), + artifact: { + path: identity.artifact.path, + sha256: identity.artifact.sha256, + }, + limitations: boundedViewMessages( + [...parent.limitations, ...limitations], + parent.evidenceId, + "limitations", + ), + unknowns: [], + }; + if (view.kind === "summary") + return ok({ + ...shared, + kind: "summary", + view, + summary, + coverage: completeWithinViewCoverage( + code_objects.length, + code_objects.length, + ), + }); + const names = view.names === undefined ? undefined : new Set(view.names); + const indices = + view.indices === undefined ? undefined : new Set(view.indices); + const fields = new Set(view.fields ?? code.keyof().options); + const projectRow = (row: PythonPycCodeObject, source_index: number) => ({ + ...Object.fromEntries( + Object.entries(row).filter(([key]) => fields.has(key)), + ), + source_index, + }); + const selected = + names === undefined && indices === undefined + ? { + rows: code_objects + .slice(view.offset, view.offset + view.limit) + .map((row, index) => projectRow(row, view.offset + index)), + total: code_objects.length, + } + : selectViewRows( + code_objects, + view, + (row, index) => + (names === undefined || names.has(row.name)) && + (indices === undefined || indices.has(index)), + projectRow, + ); + return ok({ + ...shared, + kind: "python-pyc", + view, + pyc: summary, + items: selected.rows, + coverage: pageViewCoverage( + view.offset, + selected.rows.length, + selected.total, + ), + }); +}; diff --git a/src/server/createServer.ts b/src/server/createServer.ts index 5e7448ff0e..1bf0b8938b 100644 --- a/src/server/createServer.ts +++ b/src/server/createServer.ts @@ -987,6 +987,7 @@ const registerConfiguredAnalysisTools = ( toolLogger, recordEvidence, withAdmittedAnalysis, + evidenceById, ); registerErlangTools( server, diff --git a/src/server/registerPythonPycTool.ts b/src/server/registerPythonPycTool.ts index f16e017f77..4fd06d4839 100644 --- a/src/server/registerPythonPycTool.ts +++ b/src/server/registerPythonPycTool.ts @@ -5,7 +5,14 @@ import { toolContract } from "../contracts/toolContracts.js"; import type { Logger } from "pino"; import { createEvidenceToolHandler } from "./contractToolHandler.js"; import type { WithAdmittedAnalysis } from "./analysisAdmission.js"; -import { toolRegistrationOptions } from "./toolRegistrationOptions.js"; +import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js"; +import type { EvidenceLookup } from "../application/EvidenceInputResolver.js"; +import { + requireAnalysisViewRetention, + projectRetainedAnalysis, +} from "../application/analysisView/AnalysisViewService.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; +import { err } from "../domain/result.js"; /** Register the explicit-file CPython pyc workflow and retain its complete Evidence. */ export const registerPythonPycTool = ( @@ -14,18 +21,44 @@ export const registerPythonPycTool = ( logger: Logger, recordEvidence?: EvidenceWriter["recordEvidence"], withAdmittedAnalysis?: WithAdmittedAnalysis, + evidenceById?: EvidenceLookup, ): void => { const contract = toolContract("inspect_python_pyc"); const handler = createEvidenceToolHandler( server, - (input, options) => service.inspect(input, options), + async (rawInput, options) => { + const parsed = contract.inputSchema.safeParse(rawInput); + if (!parsed.success) + return err( + analysisInputErrorFromIssues( + contract.name, + parsed.error.issues, + rawInput, + { cause: parsed.error }, + ), + ); + const { detail, view, ...input } = parsed.data; + const retention = { recordEvidence, evidenceById }; + if (detail === "summary") { + const ready = requireAnalysisViewRetention(contract.name, retention); + if (!ready.ok) return ready; + } + const inspected = await service.inspect(input, options); + return detail === "summary" && inspected.ok + ? projectRetainedAnalysis( + inspected.value, + retention, + view ?? { kind: "summary" }, + ) + : inspected; + }, logger, recordEvidence, withAdmittedAnalysis, ); server.registerTool( contract.name, - toolRegistrationOptions(contract), + applicationToolRegistrationOptions(contract), handler(contract), ); }; diff --git a/tests/acceptance/applications/pythonPycViews.test.ts b/tests/acceptance/applications/pythonPycViews.test.ts new file mode 100644 index 0000000000..788d006ee2 --- /dev/null +++ b/tests/acceptance/applications/pythonPycViews.test.ts @@ -0,0 +1,257 @@ +import { rm, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { expect } from "vitest"; +import { z } from "zod"; +import { readEvidenceBundle } from "../../../src/application/EvidenceBundleFiles.js"; +import { toolContract } from "../../../src/contracts/toolContracts.js"; +import { evidenceSchema } from "../../../src/domain/evidence.js"; +import { pythonPycSchema } from "../../../src/domain/python/pythonPyc.js"; +import { digestCanonicalValue } from "../../../src/domain/canonicalDigest.js"; +import { analysisViewResultSchema } from "../../../src/domain/analysisView/analysisView.js"; +import { + marshalCode, + pythonPycHeader, +} from "../../../src/python/PythonPyc.fixture.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; +import { cliTest, type SharedMcpCall } from "../../support/cli/cliFixture.js"; + +const pycContract = toolContract("inspect_python_pyc"); +const viewContract = toolContract("inspect_analysis_view"); + +const writeSameNamePyc = async (path: string): Promise => { + await writeFile( + path, + Buffer.concat([ + pythonPycHeader(3531), + marshalCode("py311", { + consts: [ + marshalCode("py311", { + name: "same", + qualname: "First.same", + firstlineno: 11, + }), + marshalCode("py311", { + name: "same", + qualname: "Second.same", + firstlineno: 22, + }), + ], + }), + ]), + ); +}; + +const assertSparseSelectors = async ( + call: SharedMcpCall, + source: { readonly kind: "retained-evidence"; readonly evidence_id: string }, +): Promise => { + for (const [selector, items] of [ + [{ indices: [2], fields: [] }, [{ source_index: 2 }]], + [{ names: [], fields: [] }, []], + [{ names: ["missing"], fields: [] }, []], + [{ indices: [], fields: [] }, []], + [{ names: ["same"], indices: [0], fields: [] }, []], + [ + { indices: [0], fields: ["nested_code"] }, + [{ source_index: 0, nested_code: [1, 2] }], + ], + ] as const) { + const result = await call("inspect_analysis_view", { + source, + view: { kind: "python-pyc", collection: "code_objects", ...selector }, + }); + expect(result.isError).not.toBe(true); + expect( + viewContract.outputSchema.parse(result.structuredContent) + .normalized_result, + ).toHaveProperty("items", items); + } +}; + +cliTest( + "queries same-name pyc objects through CLI/MCP after deleting the source", + { timeout: 60_000 }, + async ({ cli }) => { + const root = await createTestTempDirectory("rea-pyc-views-"); + const path = join(root, "module.pyc"); + await writeSameNamePyc(path); + const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli); + const complete = await run({ + arguments: ["inspect-python-pyc", path, "--json"], + }); + expect(complete.exitCode, complete.stderr).toBe(0); + const parent = pycContract.outputSchema.parse(complete.json); + const completeReport = pythonPycSchema.parse(parent.normalized_result); + const bundle = join(root, "complete.bundle.json"); + const summarized = await run({ + arguments: [ + "inspect-python-pyc", + path, + "--detail", + "summary", + "--evidence_output", + bundle, + "--json", + ], + }); + expect(summarized.exitCode, summarized.stderr).toBe(0); + const delivered = z + .object({ summary: evidenceSchema }) + .parse(summarized.json).summary; + expect(delivered.normalized_result).toMatchObject({ + parent_evidence_id: parent.evidence_id, + summary: { + header: completeReport.header, + coverage: { status: "complete", code_objects: 3 }, + }, + }); + expect(delivered.normalized_result).not.toHaveProperty( + "summary.code_objects", + ); + const loaded = await readEvidenceBundle(bundle); + if (!loaded.ok) throw loaded.error; + expect(loaded.value.records).toEqual([parent]); + const call = await cli.sharedMcpCall?.(); + if (call === undefined) + throw new Error("Expected shared stdio MCP session"); + const response = await call("inspect_python_pyc", { + path, + detail: "summary", + }); + expect(response.isError).not.toBe(true); + expect( + pycContract.outputSchema.parse(response.structuredContent) + .normalized_result, + ).toEqual(delivered.normalized_result); + const view = { + kind: "python-pyc", + collection: "code_objects", + names: ["same"], + fields: ["name", "qualname", "nested_code"], + offset: 0, + limit: 1, + }; + const directBundle = join(root, "selected.bundle.json"); + const directCli = await run({ + arguments: [ + "inspect-python-pyc", + path, + "--detail", + "summary", + "--view", + JSON.stringify(view), + "--evidence_output", + directBundle, + "--json", + ], + }); + expect(directCli.exitCode, directCli.stderr).toBe(0); + const directCliEvidence = z + .object({ summary: evidenceSchema }) + .parse(directCli.json).summary; + const direct = await call("inspect_python_pyc", { + path, + detail: "summary", + view, + }); + expect(direct.isError).not.toBe(true); + const directEvidence = pycContract.outputSchema.parse( + direct.structuredContent, + ); + expect(directEvidence.normalized_result).toEqual( + directCliEvidence.normalized_result, + ); + const directSaved = await readEvidenceBundle(directBundle); + if (!directSaved.ok) throw directSaved.error; + expect(directSaved.value.records).toEqual([parent]); + await rm(path); + const source = { + kind: "retained-evidence" as const, + evidence_id: parent.evidence_id, + }; + const first = await call("inspect_analysis_view", { source, view }); + expect(first.isError).not.toBe(true); + const firstPage = viewContract.outputSchema.parse(first.structuredContent); + expect(firstPage.normalized_result).toEqual( + directEvidence.normalized_result, + ); + expect(firstPage.evidence_links).toContain(parent.evidence_id); + expect(firstPage.normalized_result).toMatchObject({ + items: [ + { + source_index: 1, + name: "same", + qualname: "First.same", + nested_code: [], + }, + ], + coverage: { examined: 1, total: 2, next_offset: 1, exhausted: false }, + pyc: { coverage: { status: "complete", code_objects: 3 } }, + }); + const request = { source, view: { ...view, offset: 1 } }; + const second = await call("inspect_analysis_view", request); + expect(second.isError).not.toBe(true); + const page = viewContract.outputSchema.parse(second.structuredContent); + const projected = analysisViewResultSchema.parse(page.normalized_result); + expect(projected).toHaveProperty("items", [ + { + source_index: 2, + name: "same", + qualname: "Second.same", + nested_code: [], + }, + ]); + const { view_digest, ...content } = projected; + expect(view_digest).toBe(digestCanonicalValue(content, "Analysis view")); + const offline = await run({ + arguments: [ + "inspect-analysis-view", + JSON.stringify(request), + "--evidence-bundle", + bundle, + "--json", + ], + }); + expect(offline.exitCode, offline.stderr).toBe(0); + expect(viewContract.outputSchema.parse(offline.json)).toEqual(page); + await assertSparseSelectors(call, source); + }, +); + +cliTest( + "rejects invalid pyc delivery before reading the target", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-pyc-invalid-delivery-"); + const output = join(root, "unused.json"); + const run = cli.sharedCliRun?.bind(cli) ?? cli.run.bind(cli); + const view = JSON.stringify({ + kind: "python-pyc", + collection: "code_objects", + }); + for (const [flags, key] of [ + [["--detail", "summary"], "evidence_output"], + [ + ["--detail", "complete", "--view", view, "--evidence_output", output], + "view", + ], + [ + ["--detail", "summary", "--view", "{", "--evidence_output", output], + "view", + ], + ] as const) { + const result = await run({ + arguments: [ + "inspect-python-pyc", + "/rea-missing/module.pyc", + ...flags, + "--json", + ], + }); + expect(result.exitCode).toBe(1); + expect(result.json).toMatchObject({ + code: "invalid_request", + details: { issues: [{ path: [key] }] }, + }); + } + }, +); diff --git a/tests/acceptance/pythonPycCli.test.ts b/tests/acceptance/pythonPycCli.test.ts index 207755be5a..af8f5587d7 100644 --- a/tests/acceptance/pythonPycCli.test.ts +++ b/tests/acceptance/pythonPycCli.test.ts @@ -2,6 +2,7 @@ import { createHash } from "node:crypto"; import { mkdir, symlink, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { expect } from "vitest"; +import { pythonPycSchema } from "../../src/domain/python/pythonPyc.js"; import { toolContract } from "../../src/contracts/toolContracts.js"; import { marshalCode, @@ -11,6 +12,10 @@ import { import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js"; import { cliTest } from "../support/cli/cliFixture.js"; +const completePycEvidenceSchema = toolContract( + "inspect_python_pyc", +).outputSchema.safeExtend({ normalized_result: pythonPycSchema }); + const marshalUtf8 = (bytes: Buffer): Buffer => { const length = Buffer.alloc(4); length.writeUInt32LE(bytes.length); @@ -56,9 +61,7 @@ cliTest( arguments: ["inspect-python-pyc", path, "--json"], }); expect(result.exitCode, result.stderr).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); expect(evidence.normalized_result.code_objects[0]?.consts).toContainEqual( { kind: "int", bits, value: value.toString() }, ); @@ -78,9 +81,7 @@ cliTest( arguments: ["inspect-python-pyc", ordinaryIntPath, "--json"], }); expect(ordinaryInt.exitCode, ordinaryInt.stderr).toBe(0); - const ordinaryEvidence = toolContract( - "inspect_python_pyc", - ).outputSchema.parse(ordinaryInt.json); + const ordinaryEvidence = completePycEvidenceSchema.parse(ordinaryInt.json); expect( ordinaryEvidence.normalized_result.code_objects[0]?.consts, ).toContainEqual({ kind: "int", bits: 6, value: "-42" }); @@ -160,9 +161,7 @@ cliTest( result.exitCode, `${result.stderr}\n${JSON.stringify(result.json)}`, ).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); expect(evidence.normalized_result.code_objects[0]?.consts).toEqual([ expect.objectContaining({ kind: "frozenset", length: 3 }), ]); @@ -209,9 +208,7 @@ cliTest( result.exitCode, `${result.stderr}\n${JSON.stringify(result.json)}`, ).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); const codes = evidence.normalized_result.code_objects; expect(codes[1]?.name).toBe(codes[2]?.name); expect(codes[0]?.consts).toEqual([ @@ -230,9 +227,7 @@ cliTest( arguments: ["inspect-python-pyc", path, "--json"], }); expect(result.exitCode, result.stderr).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); expect(evidence.normalized_result.header.python_version).toBe("3.12"); expect(evidence.normalized_result.code_objects[1]?.name).toBe("inner"); }, @@ -275,9 +270,7 @@ cliTest( arguments: ["inspect-python-pyc", path, "--json"], }); expect(result.exitCode, result.stderr).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); expect(evidence.normalized_result.header.magic).toBe(magic); expect(evidence.normalized_result.code_objects[0]?.bytecode.bytes).toBe( 4, @@ -336,9 +329,7 @@ cliTest( arguments: ["inspect-python-pyc", path, "--json"], }); expect(result.exitCode, result.stderr).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); expect(evidence.normalized_result.code_objects[0]).toMatchObject( expected, ); @@ -436,9 +427,7 @@ cliTest( arguments: ["inspect-python-pyc", path, "--json"], }); expect(result.exitCode, result.stderr).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); expect(evidence.normalized_result.code_objects[0]?.firstlineno).toBe(-1); expect(evidence.normalized_result.code_objects[0]?.consts).toContainEqual({ kind: "code", @@ -488,9 +477,7 @@ cliTest( arguments: ["inspect-python-pyc", path, "--json"], }); expect(result.exitCode, result.stderr).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); expect(evidence.normalized_result.code_objects[0]?.consts).toMatchObject( testCase.expected, ); @@ -540,9 +527,7 @@ cliTest( arguments: ["inspect-python-pyc", path, "--json"], }); expect(result.exitCode, result.stderr).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); expect( evidence.normalized_result.code_objects[0]?.consts.map((value) => value.kind === "frozenset" || value.kind === "dict" @@ -623,9 +608,7 @@ cliTest( arguments: ["inspect-python-pyc", path, "--json"], }); expect(result.exitCode, result.stderr).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); expect( evidence.normalized_result.code_objects[0]?.consts.map((value) => value.kind === "frozenset" || value.kind === "dict" @@ -656,9 +639,7 @@ cliTest( arguments: ["inspect-python-pyc", rawPath, "--json"], }); expect(rawResult.exitCode, rawResult.stderr).toBe(0); - const rawEvidence = toolContract("inspect_python_pyc").outputSchema.parse( - rawResult.json, - ); + const rawEvidence = completePycEvidenceSchema.parse(rawResult.json); expect(rawEvidence.normalized_result.code_objects[0]?.flags).toBe(0x40); } }, @@ -674,9 +655,7 @@ cliTest( cwd: root, }); expect(result.exitCode, result.stderr).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); expect(evidence.normalized_result.header.python_version).toBe("3.7"); expect(evidence.subject?.local_path).toBe(join(root, "sample.pyc")); }, @@ -696,9 +675,7 @@ cliTest.skipIf(process.platform === "win32")( cwd: root, }); expect(result.exitCode, result.stderr).toBe(0); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( - result.json, - ); + const evidence = completePycEvidenceSchema.parse(result.json); expect(evidence.normalized_result.header.python_version).toBe("3.7"); expect(evidence.subject?.local_path).toBe(`${root}/link/../sample.pyc`); }, @@ -771,8 +748,9 @@ cliTest( arguments: ["inspect-python-pyc", path, "--json"], }); expect(response.exitCode, response.stderr).toBe(0); - const contract = toolContract("inspect_python_pyc"); - const report = contract.outputSchema.parse(response.json).normalized_result; + const report = completePycEvidenceSchema.parse( + response.json, + ).normalized_result; expect(report.coverage.status).toBe("complete"); expect(report.code_objects[0]?.consts).toEqual( cases.map((entry) => ({ @@ -787,7 +765,7 @@ cliTest( const mcp = await call("inspect_python_pyc", { path }); expect(mcp.isError).not.toBe(true); expect( - contract.outputSchema.parse(mcp.structuredContent).normalized_result, + completePycEvidenceSchema.parse(mcp.structuredContent).normalized_result, ).toEqual(report); const toon = await cli.run({ arguments: ["inspect-python-pyc", path, "--format", "toon"], @@ -822,8 +800,9 @@ cliTest( arguments: ["inspect-python-pyc", path, "--json"], }); expect(response.exitCode, response.stderr).toBe(0); - const schema = toolContract("inspect_python_pyc").outputSchema; - const report = schema.parse(response.json).normalized_result; + const report = completePycEvidenceSchema.parse( + response.json, + ).normalized_result; expect(report.code_objects[0]?.consts).toEqual([ { kind: "str", value: prefix, bytes: 84, truncated: true }, { kind: "str", value: prefix + "\ud800", bytes: 86, truncated: true }, @@ -832,8 +811,8 @@ cliTest( if (call === undefined) throw new Error("Expected file MCP session"); const mcp = await call("inspect_python_pyc", { path }); expect(mcp.isError).not.toBe(true); - expect(schema.parse(mcp.structuredContent).normalized_result).toEqual( - report, - ); + expect( + completePycEvidenceSchema.parse(mcp.structuredContent).normalized_result, + ).toEqual(report); }, ); diff --git a/tests/boundary/mcp/pythonPycMcp.test.ts b/tests/boundary/mcp/pythonPycMcp.test.ts index 431bfd79b0..d42ef8944b 100644 --- a/tests/boundary/mcp/pythonPycMcp.test.ts +++ b/tests/boundary/mcp/pythonPycMcp.test.ts @@ -2,6 +2,7 @@ import { writeFile } from "node:fs/promises"; import { join } from "node:path"; import { Ajv2020 } from "ajv/dist/2020.js"; import { expect } from "vitest"; +import { pythonPycSchema } from "../../../src/domain/python/pythonPyc.js"; import { toolContract } from "../../../src/contracts/toolContracts.js"; import { createServer } from "../../../src/server/createServer.js"; import { createTestBinarySession } from "../../fixtures/binarySession.js"; @@ -14,6 +15,10 @@ import { import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { mcpTest, type TestMcp } from "../../support/mcp/mcpFixture.js"; +const completePycEvidenceSchema = toolContract( + "inspect_python_pyc", +).outputSchema.safeExtend({ normalized_result: pythonPycSchema }); + const marshalInt64 = (value: bigint): Buffer => { const payload = Buffer.alloc(8); payload.writeBigInt64LE(value); @@ -66,7 +71,7 @@ mcpTest( expect(response.isError, JSON.stringify(response.content)).not.toBe( true, ); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( + const evidence = completePycEvidenceSchema.parse( response.structuredContent, ); expect( @@ -89,9 +94,9 @@ mcpTest( arguments: { path: ordinaryPath }, }); expect(ordinary.isError, JSON.stringify(ordinary.content)).not.toBe(true); - const ordinaryEvidence = toolContract( - "inspect_python_pyc", - ).outputSchema.parse(ordinary.structuredContent); + const ordinaryEvidence = completePycEvidenceSchema.parse( + ordinary.structuredContent, + ); expect( ordinaryEvidence.normalized_result.code_objects[0]?.consts, ).toContainEqual({ kind: "int", bits: 6, value: "-42" }); @@ -166,7 +171,7 @@ mcpTest( arguments: { path }, }); expect(response.isError, JSON.stringify(response.content)).not.toBe(true); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( + const evidence = completePycEvidenceSchema.parse( response.structuredContent, ); expect( @@ -264,7 +269,7 @@ mcpTest( expect(response.isError, JSON.stringify(response.content)).not.toBe( true, ); - const evidence = toolContract("inspect_python_pyc").outputSchema.parse( + const evidence = completePycEvidenceSchema.parse( response.structuredContent, ); expect(