mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 16:57:19 +00:00
feat(analysis): make resource budgets recoverable across CLI and MCP (#9706)
* refactor(analysis): centralize resource budgets and recovery facts * feat(analysis): expose ASAR and Ghidra runtime admission controls * feat(artifacts): expose reader admission and preserve limited recognition * feat(javascript): expose recoverable semantic precision budgets * feat(web): add recoverable source-map budgets and compact retained views * feat(web): make historical capture decoding budgets recoverable * fix(desktop): enforce selected view policies across capture and export * feat(runtime): make Frida and LLDB retention budgets selectable * feat(native): expose diagnostic budgets and preserve exhaustion causes * feat(managed): expose recoverable snapshot and representation budgets * feat(metadata): make Go and Rust inspection budgets recoverable * feat(wasm): expose representation and producer admissions * feat(evm): expose per-call worker resource budgets * feat(android): make resource and string-pool omissions recoverable * feat(producers): expose command and recorded-capture budgets * docs(analysis): record recovery contracts and integrated validation * fix(docs): link calibration oracle from published verification pages * fix(docs): restore desktop task anchors and receipt links * test(analysis): align fixtures with selectable budget contracts * fix(analysis): remove source-location projector dependency cycle * test(analysis): refresh budget recovery and verifier regressions * test(analysis): preserve format and publication recovery expectations * test(windows): align conformance with owned runtime admission * fix(desktop): preserve selected alias kinds in exported state * test(analysis): align provider fixtures with recoverable budgets * test(desktop): validate emitted alias metadata structurally * fix(ghidra): classify bounded Windows endpoint refusals * test(cli): assert selectable artifact admission recovery * fix(cli): preserve capture provenance in environment builds
This commit is contained in:
1 parent
55b6d2ca20
commit
cb4e5cfbff
515 files changed
+21066
-3637
No files matched your search
@@ -0,0 +1,214 @@
|
||||
"""Observe one selected experiment's process group and descendant tree.
|
||||
|
||||
macOS has no cgroup peak counter. RSS is a sampled sum, not an exact kernel
|
||||
peak or aggregate memory hard limit. Only the owned group is stopped early;
|
||||
descendant-tree sampling is observational and does not confer signal authority.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import signal
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
def process_snapshot():
|
||||
rows = subprocess.run(
|
||||
["ps", "-axo", "pid=,ppid=,pgid=,rss="],
|
||||
text=True, capture_output=True, check=True,
|
||||
).stdout.splitlines()
|
||||
return [tuple(map(int, row.split())) for row in rows]
|
||||
|
||||
|
||||
def group_rss(group, snapshot=None):
|
||||
if snapshot is None:
|
||||
snapshot = process_snapshot()
|
||||
return [(pid, kib * 1024) for pid, _, pgid, kib in snapshot if pgid == group]
|
||||
|
||||
|
||||
def tree_rss(root, snapshot):
|
||||
children = {}
|
||||
for pid, ppid, _, _ in snapshot:
|
||||
children.setdefault(ppid, []).append(pid)
|
||||
pending = [root]
|
||||
descendants = set()
|
||||
while pending:
|
||||
pid = pending.pop()
|
||||
if pid not in descendants:
|
||||
descendants.add(pid)
|
||||
pending.extend(children.get(pid, []))
|
||||
return [(pid, kib * 1024) for pid, _, _, kib in snapshot if pid in descendants]
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--output", required=True)
|
||||
parser.add_argument("--cwd", required=True)
|
||||
parser.add_argument("--timeout-seconds", type=float, default=90)
|
||||
parser.add_argument("--memory-mib", type=int, default=1536)
|
||||
parser.add_argument("command", nargs=argparse.REMAINDER)
|
||||
options = parser.parse_args()
|
||||
command = options.command
|
||||
if command[:1] == ["--"]:
|
||||
command = command[1:]
|
||||
if not command or options.timeout_seconds <= 0 or options.memory_mib <= 0:
|
||||
parser.error("Select an argv target and positive observation bounds")
|
||||
output = Path(options.output).resolve()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
stdout_path = output.with_suffix(".stdout")
|
||||
stderr_path = output.with_suffix(".stderr")
|
||||
peak = 0
|
||||
maximum_members = 0
|
||||
tree_peak = 0
|
||||
maximum_tree_members = 0
|
||||
samples = 0
|
||||
stopped = None
|
||||
usage = None
|
||||
status = None
|
||||
signal_errors = []
|
||||
observer_error = None
|
||||
started = time.monotonic()
|
||||
with stdout_path.open("wb") as stdout, stderr_path.open("wb") as stderr:
|
||||
process = subprocess.Popen(
|
||||
command, cwd=options.cwd, stdout=stdout, stderr=stderr,
|
||||
start_new_session=True,
|
||||
)
|
||||
|
||||
def reap():
|
||||
nonlocal status, usage
|
||||
if process.returncode is not None:
|
||||
return True
|
||||
pid, observed_status, observed_usage = os.wait4(process.pid, os.WNOHANG)
|
||||
if not pid:
|
||||
return False
|
||||
status, usage = observed_status, observed_usage
|
||||
process.returncode = os.waitstatus_to_exitcode(status)
|
||||
return True
|
||||
|
||||
def root_exited():
|
||||
# Keep the session leader waitable until group cleanup finishes.
|
||||
# Its reserved PID prevents our process-group identity being reused.
|
||||
return os.waitid(
|
||||
os.P_PID, process.pid, os.WEXITED | os.WNOHANG | os.WNOWAIT,
|
||||
) is not None
|
||||
|
||||
def group_has_work():
|
||||
exited = root_exited()
|
||||
return any(
|
||||
pid != process.pid or not exited
|
||||
for pid, _ in group_rss(process.pid)
|
||||
)
|
||||
|
||||
def signal_owned_group(selected_signal):
|
||||
# A sample and the following signal are not atomic. A completed
|
||||
# root may still own live children. Do not reap it before their
|
||||
# cleanup, and preserve receipts when the OS refuses a signal.
|
||||
if not group_has_work():
|
||||
return
|
||||
try:
|
||||
os.killpg(process.pid, selected_signal)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
except OSError as error:
|
||||
signal_errors.append({
|
||||
"signal": selected_signal.name,
|
||||
"errno": error.errno,
|
||||
"reason": str(error),
|
||||
})
|
||||
|
||||
def stop_owned_group():
|
||||
signal_owned_group(signal.SIGTERM)
|
||||
time.sleep(0.2)
|
||||
signal_owned_group(signal.SIGKILL)
|
||||
deadline = time.monotonic() + 1
|
||||
while group_has_work() and time.monotonic() < deadline:
|
||||
time.sleep(0.02)
|
||||
reap()
|
||||
|
||||
try:
|
||||
while True:
|
||||
if root_exited():
|
||||
if group_has_work():
|
||||
stopped = "target_exited_with_group_members"
|
||||
stop_owned_group()
|
||||
else:
|
||||
reap()
|
||||
break
|
||||
# One OS snapshot supplies both scopes. Ancestry is an
|
||||
# observation, never permission to signal detached children.
|
||||
snapshot = process_snapshot()
|
||||
members = group_rss(process.pid, snapshot)
|
||||
tree_members = tree_rss(process.pid, snapshot)
|
||||
samples += 1
|
||||
peak = max(peak, sum(rss for _, rss in members))
|
||||
maximum_members = max(maximum_members, len(members))
|
||||
tree_peak = max(tree_peak, sum(rss for _, rss in tree_members))
|
||||
maximum_tree_members = max(maximum_tree_members, len(tree_members))
|
||||
elapsed = time.monotonic() - started
|
||||
if peak > options.memory_mib * 1024 * 1024:
|
||||
stopped = "sampled_group_rss_early_stop"
|
||||
elif elapsed > options.timeout_seconds:
|
||||
stopped = "wall_deadline"
|
||||
if stopped:
|
||||
stop_owned_group()
|
||||
break
|
||||
time.sleep(0.02)
|
||||
except BaseException as error:
|
||||
observer_error = {"type": type(error).__name__, "reason": str(error)}
|
||||
stopped = stopped or "observer_error"
|
||||
stop_owned_group()
|
||||
remaining = group_rss(process.pid)
|
||||
receipt = {
|
||||
"schema": "rea-budget-calibration/v1",
|
||||
"argv": command,
|
||||
"cwd": options.cwd,
|
||||
"platform": platform.platform(),
|
||||
"elapsed_seconds": time.monotonic() - started,
|
||||
"cpu_seconds_waited_subtree": usage.ru_utime + usage.ru_stime if usage else None,
|
||||
"peak_process_group_rss_bytes_sampled": peak,
|
||||
"maximum_group_members_sampled": maximum_members,
|
||||
"peak_process_tree_rss_bytes_sampled": tree_peak,
|
||||
"maximum_tree_members_sampled": maximum_tree_members,
|
||||
"sample_count": samples,
|
||||
"sample_interval_seconds": 0.02,
|
||||
"memory_early_stop_bytes": options.memory_mib * 1024 * 1024,
|
||||
"memory_early_stop_scope": "owned_process_group",
|
||||
"wall_deadline_seconds": options.timeout_seconds,
|
||||
"wall_deadline_signal_scope": "owned_process_group",
|
||||
"stop_reason": stopped,
|
||||
"signal_errors": signal_errors,
|
||||
"observer_error": observer_error,
|
||||
"exit_code": process.returncode,
|
||||
"cleanup": "group_empty" if not remaining else "group_members_remaining",
|
||||
"cleanup_scope": "owned_process_group",
|
||||
"remaining_group_pids": [pid for pid, _ in remaining],
|
||||
"stdout": str(stdout_path),
|
||||
"stderr": str(stderr_path),
|
||||
"stdout_sha256": hashlib.sha256(stdout_path.read_bytes()).hexdigest(),
|
||||
"stderr_sha256": hashlib.sha256(stderr_path.read_bytes()).hexdigest(),
|
||||
"limitations": [
|
||||
"RSS scopes are simultaneous sums within each ps snapshot; short peaks can be missed.",
|
||||
"Shared pages can be counted in multiple members' RSS.",
|
||||
"Group RSS excludes descendants that leave this group; tree RSS includes detached descendants while PPID ancestry remains linked to the waitable root.",
|
||||
"Tree samples can miss exited, reparented, or orphaned descendants; no cumulative PID list or descendant signal authority is inferred.",
|
||||
"CPU is OS wait4 user+system usage of the waited subtree; sampler CPU is excluded.",
|
||||
"CPU excludes descendants the target did not wait for, including orphaned children.",
|
||||
"Memory early stop and cleanup signals apply only to the owned group, not the observed tree; neither RSS metric is a kernel hard limit.",
|
||||
"Wall time includes observation overhead; OS caches are not flushed.",
|
||||
],
|
||||
}
|
||||
output.write_text(json.dumps(receipt, indent=2) + "\n")
|
||||
print(json.dumps({key: receipt[key] for key in [
|
||||
"elapsed_seconds", "cpu_seconds_waited_subtree",
|
||||
"peak_process_group_rss_bytes_sampled", "exit_code", "cleanup", "stop_reason",
|
||||
"peak_process_tree_rss_bytes_sampled",
|
||||
]}))
|
||||
return process.returncode if not stopped and not remaining and process.returncode is not None else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Join serial observer receipts with exact tokenization of every logical call.
|
||||
|
||||
Requires tiktoken 0.12.0 for comparison with the retained measurements.
|
||||
Token counts exclude chat framing, catalogs, adapters, retained delivery and
|
||||
model reasoning. Artifact and repository path spelling can affect tokens.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import tiktoken
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('receipts', type=Path)
|
||||
parser.add_argument('output', type=Path)
|
||||
options = parser.parse_args()
|
||||
encoding = tiktoken.get_encoding('cl100k_base')
|
||||
rows = []
|
||||
for path in sorted(options.receipts.rglob('*.metrics.json')):
|
||||
metrics = json.loads(path.read_text())
|
||||
key = str(path.relative_to(options.receipts)).removesuffix('.metrics.json')
|
||||
# The observer argv ends with the selected configuration file.
|
||||
config = json.loads(Path(metrics['argv'][-1]).read_text())
|
||||
reply = json.loads(Path(config['reply_receipt']).read_text())
|
||||
assert metrics['exit_code'] == 0 and metrics['stop_reason'] is None, path
|
||||
assert metrics['cleanup'] == 'group_empty' and reply['success'], path
|
||||
tokens = sum(
|
||||
len(encoding.encode(json.dumps(call[field], ensure_ascii=False, separators=(',', ':'))))
|
||||
for call in reply['calls']
|
||||
for field in ['request', 'reply']
|
||||
)
|
||||
rows.append({
|
||||
'key': key,
|
||||
'calls': reply['call_count'],
|
||||
'tokens_cl100k_base': tokens,
|
||||
'seconds': metrics['elapsed_seconds'],
|
||||
'cpu': metrics['cpu_seconds_waited_subtree'],
|
||||
'peak_mib': metrics['peak_process_group_rss_bytes_sampled'] / 1024**2,
|
||||
'delivered': reply['package_uris_delivered_final'],
|
||||
'cleanup': metrics['cleanup'],
|
||||
'scope': reply['boundary'],
|
||||
})
|
||||
assert rows, 'No *.metrics.json receipts found'
|
||||
options.output.write_text(json.dumps(rows, indent=2) + '\n')
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Generate eight task configs after oracle.py has verified the pinned APKs."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('inputs', type=Path)
|
||||
parser.add_argument('output', type=Path)
|
||||
options = parser.parse_args()
|
||||
options.output.mkdir(parents=True, exist_ok=True)
|
||||
manifest = json.loads((Path(__file__).parent / 'manifest.json').read_text())
|
||||
cases = [
|
||||
('inventory-default-retry', 'package-inventory', None, True),
|
||||
('inventory-2048', 'package-inventory', 2048, False),
|
||||
('overview-default', 'overview', None, False),
|
||||
('overview-2048', 'overview', 2048, False),
|
||||
]
|
||||
for item in manifest['artifacts']:
|
||||
stem = Path(item['file']).stem
|
||||
oracle = (options.output / f'{stem}.oracle.json').resolve()
|
||||
if not oracle.exists():
|
||||
parser.error(f'Run oracle.py first: {oracle}')
|
||||
for case, task, limit, recover in cases:
|
||||
config = {
|
||||
'artifact': str((options.inputs / item['file']).resolve()),
|
||||
'oracle': str(oracle),
|
||||
'task': task,
|
||||
'recover': recover,
|
||||
'recovery_uri_limit': 2048,
|
||||
'reply_receipt': str((options.output / f'{stem}.{case}.replies.json').resolve()),
|
||||
}
|
||||
if limit is not None:
|
||||
config['first_uri_limit'] = limit
|
||||
(options.output / f'{stem}.{case}.config.json').write_text(
|
||||
json.dumps(config, indent=2) + '\n'
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"file": "flutter_gallery_Android.apk",
|
||||
"url": "https://github.com/flutter-team-archive/gallery/releases/download/v2.9.2/flutter_gallery_Android.apk",
|
||||
"bytes": 117817550,
|
||||
"sha256": "659b5fc6a4319dfe4f117d00ad4e7ec004915ebcadcb96628797fb3544040e29",
|
||||
"expected_libapp_sha256": "0ae30443b66dcf579d024d2f121f77204639bda9897116a8b2f66a6385189fd9",
|
||||
"expected_package_count_1024": 756,
|
||||
"expected_dart_count_1024": 201,
|
||||
"expected_isolate_section": {
|
||||
"name": "_kDartIsolateSnapshotData",
|
||||
"offset": 10420224,
|
||||
"size": 14263200
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "LocalSend-1.18.2-android-arm64v8.apk",
|
||||
"url": "https://github.com/localsend/localsend/releases/download/v1.18.2/LocalSend-1.18.2-android-arm64v8.apk",
|
||||
"bytes": 46558706,
|
||||
"sha256": "82ec3568fba2aa5295b9aae8b76f701d7a4703d86b9f8bad749472038fbaeab3",
|
||||
"expected_libapp_sha256": "8f46efd1f78d65ebc0ff630655f274f3c36be89aa13316fb7322a3dda8a497d6",
|
||||
"expected_package_count_1024": 1407,
|
||||
"expected_dart_count_1024": 216,
|
||||
"expected_isolate_section": {
|
||||
"name": "_kDartIsolateSnapshotData",
|
||||
"offset": 19264,
|
||||
"size": 6742144
|
||||
}
|
||||
}
|
||||
],
|
||||
"supporting_artifacts": [
|
||||
{
|
||||
"file": "qgis-world-map.gpkg",
|
||||
"url": "https://raw.githubusercontent.com/qgis/QGIS/e1223b8e9e71fc50ee995ed556f711c54ccc101b/resources/data/world_map.gpkg",
|
||||
"bytes": 12914688,
|
||||
"sha256": "01d293159d1b77bd9212b5252587853f61aa9e25b2387135f34afaba8d057190",
|
||||
"independent_sqlite_row_counts": {
|
||||
"declared_tables_including_virtual_and_shadow": 26,
|
||||
"countries": 240,
|
||||
"states_provinces": 4556,
|
||||
"disputed_borders": 46,
|
||||
"layer_styles": 4
|
||||
},
|
||||
"role": "Supporting counts only, not a new timed desktop calibration"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
{
|
||||
"schema": "rea-flutter-budget-calibration/v1",
|
||||
"baseline": {
|
||||
"head": "e2a2ede5440df396e44b3406ef511a31a680290e",
|
||||
"tracked_patch_sha256": "038b69c19bbd79a303c9ecd8d9d22a1251637e3886543ca4f5a4c1b70c724eec",
|
||||
"description": "Frozen in-progress source snapshot; HEAD alone does not identify this runtime."
|
||||
},
|
||||
"host": "macOS-26.3-arm64-arm-64bit-Mach-O",
|
||||
"node": "24.22.1",
|
||||
"driver_original_sha256": "b696f10ddef5bab2dc966587bf362a4f93e7a5c662db1f4c12d107b090e78cba",
|
||||
"oracle_original_sha256": "82e7e68bea71929ebfce3e607d13bea88fb6bd0259892bfa3655d2cd17a1a49a",
|
||||
"tokenizer": {
|
||||
"encoding": "cl100k_base",
|
||||
"tiktoken_version": "0.12.0",
|
||||
"scope": "logical request and reply JSON, including retries; no chat framing/catalog/adapters/model usage",
|
||||
"serialization": "json.dumps(call[field], ensure_ascii=False, separators=(',', ':')) for request and reply separately on every call"
|
||||
},
|
||||
"method": {
|
||||
"trials": 3,
|
||||
"cases": 8,
|
||||
"cache": "fresh process per case; OS caches not flushed; no cold-cache or warm-runtime-cache claims",
|
||||
"process_group_rss_sample_seconds": 0.02,
|
||||
"cpu": "wait4 waited-subtree user+system; sampler excluded",
|
||||
"memory_early_stop_mib": 1536,
|
||||
"wall_deadline_seconds": 90,
|
||||
"memory_limit_note": "sampled early-stop threshold, not kernel hard limit"
|
||||
},
|
||||
"runs": [
|
||||
{
|
||||
"key": "r1-LocalSend-1.18.2-android-arm64v8.inventory-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 16317,
|
||||
"seconds": 0.299053416994866,
|
||||
"cpu": 0.374338,
|
||||
"peak_mib": 184.78125,
|
||||
"delivered": 1407,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r1-LocalSend-1.18.2-android-arm64v8.inventory-default-retry",
|
||||
"calls": 2,
|
||||
"tokens_cl100k_base": 22360,
|
||||
"seconds": 0.40635258400288876,
|
||||
"cpu": 0.515262,
|
||||
"peak_mib": 221.328125,
|
||||
"delivered": 1407,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r1-LocalSend-1.18.2-android-arm64v8.overview-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 27158,
|
||||
"seconds": 0.29257937498914544,
|
||||
"cpu": 0.388501,
|
||||
"peak_mib": 189.515625,
|
||||
"delivered": 1407,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r1-LocalSend-1.18.2-android-arm64v8.overview-default",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 16885,
|
||||
"seconds": 0.2934171250090003,
|
||||
"cpu": 0.413556,
|
||||
"peak_mib": 189.625,
|
||||
"delivered": 500,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r1-flutter_gallery_Android.inventory-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 8631,
|
||||
"seconds": 0.5524000000004889,
|
||||
"cpu": 0.598673,
|
||||
"peak_mib": 238.859375,
|
||||
"delivered": 756,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r1-flutter_gallery_Android.inventory-default-retry",
|
||||
"calls": 2,
|
||||
"tokens_cl100k_base": 14488,
|
||||
"seconds": 1.01431600000069,
|
||||
"cpu": 1.250315,
|
||||
"peak_mib": 299.453125,
|
||||
"delivered": 756,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r1-flutter_gallery_Android.overview-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 18764,
|
||||
"seconds": 0.5649452920042677,
|
||||
"cpu": 0.639156,
|
||||
"peak_mib": 243.328125,
|
||||
"delivered": 756,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r1-flutter_gallery_Android.overview-default",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 15995,
|
||||
"seconds": 0.5576133750000736,
|
||||
"cpu": 0.6388879999999999,
|
||||
"peak_mib": 240.71875,
|
||||
"delivered": 500,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r2-LocalSend-1.18.2-android-arm64v8.inventory-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 16317,
|
||||
"seconds": 0.2958210000069812,
|
||||
"cpu": 0.36518599999999996,
|
||||
"peak_mib": 183.703125,
|
||||
"delivered": 1407,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r2-LocalSend-1.18.2-android-arm64v8.inventory-default-retry",
|
||||
"calls": 2,
|
||||
"tokens_cl100k_base": 22360,
|
||||
"seconds": 0.4046505839942256,
|
||||
"cpu": 0.530767,
|
||||
"peak_mib": 216.390625,
|
||||
"delivered": 1407,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r2-LocalSend-1.18.2-android-arm64v8.overview-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 27158,
|
||||
"seconds": 0.3230182090046583,
|
||||
"cpu": 0.38625,
|
||||
"peak_mib": 189.640625,
|
||||
"delivered": 1407,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r2-LocalSend-1.18.2-android-arm64v8.overview-default",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 16885,
|
||||
"seconds": 0.289660875001573,
|
||||
"cpu": 0.400553,
|
||||
"peak_mib": 189.765625,
|
||||
"delivered": 500,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r2-flutter_gallery_Android.inventory-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 8631,
|
||||
"seconds": 0.47969675000058487,
|
||||
"cpu": 0.606711,
|
||||
"peak_mib": 241.25,
|
||||
"delivered": 756,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r2-flutter_gallery_Android.inventory-default-retry",
|
||||
"calls": 2,
|
||||
"tokens_cl100k_base": 14488,
|
||||
"seconds": 0.8361371669889195,
|
||||
"cpu": 1.028465,
|
||||
"peak_mib": 258.65625,
|
||||
"delivered": 756,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r2-flutter_gallery_Android.overview-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 18764,
|
||||
"seconds": 0.5597699580102926,
|
||||
"cpu": 0.633868,
|
||||
"peak_mib": 241.53125,
|
||||
"delivered": 756,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r2-flutter_gallery_Android.overview-default",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 15995,
|
||||
"seconds": 0.4476122909982223,
|
||||
"cpu": 0.641888,
|
||||
"peak_mib": 245.84375,
|
||||
"delivered": 500,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r3-LocalSend-1.18.2-android-arm64v8.inventory-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 16317,
|
||||
"seconds": 0.29069650000019465,
|
||||
"cpu": 0.362932,
|
||||
"peak_mib": 185.859375,
|
||||
"delivered": 1407,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r3-LocalSend-1.18.2-android-arm64v8.inventory-default-retry",
|
||||
"calls": 2,
|
||||
"tokens_cl100k_base": 22360,
|
||||
"seconds": 0.3999212499911664,
|
||||
"cpu": 0.519303,
|
||||
"peak_mib": 230.84375,
|
||||
"delivered": 1407,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r3-LocalSend-1.18.2-android-arm64v8.overview-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 27158,
|
||||
"seconds": 0.2895570419932483,
|
||||
"cpu": 0.388245,
|
||||
"peak_mib": 189.984375,
|
||||
"delivered": 1407,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r3-LocalSend-1.18.2-android-arm64v8.overview-default",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 16885,
|
||||
"seconds": 0.29197387500607874,
|
||||
"cpu": 0.402588,
|
||||
"peak_mib": 190.25,
|
||||
"delivered": 500,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r3-flutter_gallery_Android.inventory-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 8631,
|
||||
"seconds": 0.447617124998942,
|
||||
"cpu": 0.600641,
|
||||
"peak_mib": 238.6875,
|
||||
"delivered": 756,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r3-flutter_gallery_Android.inventory-default-retry",
|
||||
"calls": 2,
|
||||
"tokens_cl100k_base": 14488,
|
||||
"seconds": 0.8252949169982458,
|
||||
"cpu": 1.0068,
|
||||
"peak_mib": 254.765625,
|
||||
"delivered": 756,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r3-flutter_gallery_Android.overview-2048",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 18764,
|
||||
"seconds": 0.4492089169943938,
|
||||
"cpu": 0.6431779999999999,
|
||||
"peak_mib": 249.65625,
|
||||
"delivered": 756,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
},
|
||||
{
|
||||
"key": "r3-flutter_gallery_Android.overview-default",
|
||||
"calls": 1,
|
||||
"tokens_cl100k_base": 15995,
|
||||
"seconds": 0.44429433399636764,
|
||||
"cpu": 0.6304479999999999,
|
||||
"peak_mib": 241.921875,
|
||||
"delivered": 500,
|
||||
"cleanup": "group_empty",
|
||||
"scope": "shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
"""Independently enumerate bounded printable runs in two pinned Flutter APKs.
|
||||
|
||||
This intentionally small ELF64LE parser is for these verified artifacts, not a
|
||||
replacement for production parsing. It uses no REA implementation code.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import struct
|
||||
import zipfile
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('inputs', type=Path)
|
||||
parser.add_argument('output', type=Path)
|
||||
options = parser.parse_args()
|
||||
options.output.mkdir(parents=True, exist_ok=True)
|
||||
manifest = json.loads((Path(__file__).parent / 'manifest.json').read_text())
|
||||
for item in manifest['artifacts']:
|
||||
apk = options.inputs / item['file']
|
||||
assert apk.stat().st_size == item['bytes'], apk
|
||||
with apk.open('rb') as source:
|
||||
artifact_digest = hashlib.file_digest(source, 'sha256').hexdigest()
|
||||
assert artifact_digest == item['sha256'], apk
|
||||
with zipfile.ZipFile(apk) as archive:
|
||||
data = archive.read('lib/arm64-v8a/libapp.so')
|
||||
assert data[:6] == b'\x7fELF\x02\x01', apk
|
||||
section_offset = struct.unpack_from('<Q', data, 40)[0]
|
||||
section_size, section_count = struct.unpack_from('<HH', data, 58)
|
||||
sections = [
|
||||
struct.unpack_from('<IIQQQQIIQQ', data, section_offset + i * section_size)
|
||||
for i in range(section_count)
|
||||
]
|
||||
found = []
|
||||
for section in sections:
|
||||
if section[1] != 11: # SHT_DYNSYM
|
||||
continue
|
||||
strings = sections[section[6]]
|
||||
names = data[strings[4]:strings[4] + strings[5]]
|
||||
for at in range(section[4], section[4] + section[5], section[9]):
|
||||
name, _, _, index, value, size = struct.unpack_from('<IBBHQQ', data, at)
|
||||
end = names.find(b'\0', name)
|
||||
if names[name:end] != b'_kDartIsolateSnapshotData':
|
||||
continue
|
||||
owner = sections[index]
|
||||
offset = owner[4] + value - owner[3]
|
||||
assert 0 <= offset <= len(data) - size
|
||||
found.append((offset, size))
|
||||
assert len(found) == 1, (apk, found)
|
||||
offset, size = found[0]
|
||||
runs = re.findall(rb'[\x20-\x7e]+', data[offset:offset + size])
|
||||
oracle = {
|
||||
'artifact': str(apk),
|
||||
'artifact_sha256': artifact_digest,
|
||||
'libapp_sha256': hashlib.sha256(data).hexdigest(),
|
||||
'abi': 'arm64-v8a',
|
||||
'section': {
|
||||
'name': '_kDartIsolateSnapshotData',
|
||||
'offset': offset,
|
||||
'size': size,
|
||||
},
|
||||
'oracle': 'Independent Python ELF DYNSYM/section mapping and byte regex; no REA parser used',
|
||||
'policies': {},
|
||||
}
|
||||
for run_bytes in [1024, 4096]:
|
||||
selected = [run for run in runs if 5 <= len(run) <= run_bytes]
|
||||
packages = sorted({run.decode('ascii') for run in selected if run.startswith(b'package:')})
|
||||
darts = sorted({run.decode('ascii') for run in selected if run.startswith(b'dart:')})
|
||||
oracle['policies'][str(run_bytes)] = {
|
||||
'package_uris': packages,
|
||||
'dart_uris': darts,
|
||||
'package_count': len(packages),
|
||||
'dart_count': len(darts),
|
||||
'longest_printable_run': max(map(len, runs)),
|
||||
'overflowed_runs': sum(len(run) > run_bytes for run in runs),
|
||||
}
|
||||
assert oracle['libapp_sha256'] == item['expected_libapp_sha256']
|
||||
assert oracle['section'] == item['expected_isolate_section']
|
||||
assert oracle['policies']['1024']['package_count'] == item['expected_package_count_1024']
|
||||
assert oracle['policies']['1024']['dart_count'] == item['expected_dart_count_1024']
|
||||
target = options.output / (apk.stem + '.oracle.json')
|
||||
target.write_text(json.dumps(oracle, indent=2) + '\n')
|
||||
print(json.dumps({
|
||||
'oracle': str(target),
|
||||
'artifact_sha256': artifact_digest,
|
||||
'libapp_sha256': oracle['libapp_sha256'],
|
||||
'section': oracle['section'],
|
||||
'policies': {
|
||||
key: {name: value for name, value in policy.items() if not isinstance(value, list)}
|
||||
for key, policy in oracle['policies'].items()
|
||||
},
|
||||
}))
|
||||
@@ -0,0 +1,103 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { join } from "node:path";
|
||||
import assert from "node:assert/strict";
|
||||
const [repository, configPath] = process.argv.slice(2);
|
||||
const config = JSON.parse(await readFile(configPath, "utf8"));
|
||||
const { FlutterBuildAnalysisService } = await import(
|
||||
pathToFileURL(
|
||||
join(repository, "src/application/flutter/FlutterBuildAnalysisService.ts"),
|
||||
)
|
||||
);
|
||||
const { createFlutterBuildAnalysisProvider } = await import(
|
||||
pathToFileURL(join(repository, "src/composition/flutter.ts"))
|
||||
);
|
||||
const { estimateTokenCount } = await import(
|
||||
pathToFileURL(join(repository, "node_modules/tokenx/dist/index.mjs"))
|
||||
);
|
||||
const oracle = JSON.parse(await readFile(config.oracle, "utf8"));
|
||||
const calls = [];
|
||||
const provider = createFlutterBuildAnalysisProvider();
|
||||
const service = new FlutterBuildAnalysisService(provider);
|
||||
try {
|
||||
let uriLimit = config.first_uri_limit;
|
||||
for (let attempt = 0; attempt < 5; attempt++) {
|
||||
const input = {
|
||||
path: config.artifact,
|
||||
abi: "arm64-v8a",
|
||||
pool_policy: {
|
||||
...(uriLimit === undefined ? {} : { uri_limit: uriLimit }),
|
||||
...(config.task === "package-inventory"
|
||||
? { contains: "package:" }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
const request = { operation: "inspect_dart_aot", input };
|
||||
const reply = await service.execute(request.operation, request.input);
|
||||
calls.push({
|
||||
request,
|
||||
reply,
|
||||
request_token_estimate: estimateTokenCount(JSON.stringify(request)),
|
||||
reply_token_estimate: estimateTokenCount(JSON.stringify(reply)),
|
||||
});
|
||||
assert.equal(reply.ok, true, JSON.stringify(reply));
|
||||
const result = reply.value.normalized_result;
|
||||
assert.equal(result.target.sha256, oracle.artifact_sha256);
|
||||
assert.equal(result.libapp.sha256, oracle.libapp_sha256);
|
||||
assert.equal(result.abi, oracle.abi);
|
||||
assert.equal(result.string_pool.policy.run_bytes, 1024);
|
||||
assert.equal(result.string_pool.policy.token_bytes, 128);
|
||||
assert.equal(result.string_pool.policy.token_limit, 2000);
|
||||
if (config.task === "package-inventory") {
|
||||
const expected = oracle.policies["1024"].package_uris;
|
||||
if (
|
||||
result.string_pool.package_uris.length <
|
||||
result.string_pool.package_uri_count_at_least
|
||||
) {
|
||||
assert.equal(
|
||||
config.recover,
|
||||
true,
|
||||
"Candidate failed inventory completeness",
|
||||
);
|
||||
uriLimit = config.recovery_uri_limit;
|
||||
continue;
|
||||
}
|
||||
assert.deepEqual([...result.string_pool.package_uris].sort(), expected);
|
||||
} else {
|
||||
// Overview controls verify identity, ABI and snapshot section identities;
|
||||
// they deliberately do not require any printable string values.
|
||||
assert.ok(result.libapp.sections.length > 0);
|
||||
}
|
||||
const tokenEstimate = calls.reduce(
|
||||
(n, call) => n + call.request_token_estimate + call.reply_token_estimate,
|
||||
0,
|
||||
);
|
||||
const receipt = {
|
||||
task: config.task,
|
||||
boundary:
|
||||
"shared application service; excludes CLI/MCP discovery, adapter delivery, retained resource overhead and model reasoning",
|
||||
token_estimator:
|
||||
"tokenx 1.3.0, estimate only; all logical requests and replies including retries",
|
||||
token_estimate: tokenEstimate,
|
||||
call_count: calls.length,
|
||||
success: true,
|
||||
pool_coverage: result.string_pool.coverage,
|
||||
package_uris_delivered_final: result.string_pool.package_uris.length,
|
||||
package_uri_count_at_least_final:
|
||||
result.string_pool.package_uri_count_at_least,
|
||||
config,
|
||||
calls,
|
||||
};
|
||||
await writeFile(
|
||||
config.reply_receipt,
|
||||
JSON.stringify(receipt, null, 2) + "\n",
|
||||
);
|
||||
process.stdout.write(
|
||||
JSON.stringify({ ...receipt, calls: undefined }) + "\n",
|
||||
);
|
||||
break;
|
||||
}
|
||||
assert.ok(calls.length < 5, "Recovery did not complete");
|
||||
} finally {
|
||||
await provider.close();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Aggregate retained observer receipts and complete CLI records; needs tiktoken."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import statistics
|
||||
|
||||
import tiktoken
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('inputs', type=Path)
|
||||
parser.add_argument('output', type=Path)
|
||||
options = parser.parse_args()
|
||||
encoder = tiktoken.get_encoding('cl100k_base')
|
||||
def compact(value):
|
||||
return json.dumps(value, ensure_ascii=False, separators=(',', ':'))
|
||||
rows = []
|
||||
for path in sorted(options.inputs.glob('*.metrics.json')):
|
||||
metrics = json.loads(path.read_text())
|
||||
record = json.loads(path.with_name(path.name.replace('.metrics.json', '.replies.json')).read_text())
|
||||
summary = json.loads(path.with_suffix('.stdout').read_text())
|
||||
tokens = {
|
||||
'arguments': len(encoder.encode(compact(record['argv']))),
|
||||
'reply': len(encoder.encode(compact(json.loads(record['stdout'])))),
|
||||
'progress_stderr': len(encoder.encode(record['stderr'])),
|
||||
}
|
||||
rows.append({
|
||||
'name': path.stem.removesuffix('.metrics'),
|
||||
'task': summary['case'], 'budget': summary['budget'],
|
||||
'verified_facts': summary['verified_facts'], 'unknowns': summary['unknowns'],
|
||||
'wall_s': metrics['elapsed_seconds'],
|
||||
'cpu_s': metrics['cpu_seconds_waited_subtree'],
|
||||
'host_group_rss_bytes': metrics['peak_process_group_rss_bytes_sampled'],
|
||||
'tokens': tokens, 'reply_bytes': summary['reply_bytes'],
|
||||
'exit_code': metrics['exit_code'], 'cleanup': metrics['cleanup'],
|
||||
'stop_reason': metrics['stop_reason'],
|
||||
})
|
||||
medians = []
|
||||
for task in ['theme', 'ms']:
|
||||
for budget in ['default', 'double']:
|
||||
selected = [row for row in rows if row['task'] == task and row['budget'] == budget]
|
||||
if not selected:
|
||||
continue
|
||||
medians.append({
|
||||
'task': task, 'budget': budget, 'trials': len(selected),
|
||||
'facts': selected[0]['verified_facts'], 'unknowns': selected[0]['unknowns'],
|
||||
'wall_s': statistics.median(row['wall_s'] for row in selected),
|
||||
'cpu_s': statistics.median(row['cpu_s'] for row in selected),
|
||||
'host_group_rss_mib': statistics.median(row['host_group_rss_bytes'] / 1048576 for row in selected),
|
||||
'tokens': statistics.median(sum(row['tokens'].values()) for row in selected),
|
||||
'reply_bytes': selected[0]['reply_bytes'],
|
||||
})
|
||||
options.output.write_text(json.dumps({'rows': rows, 'medians': medians}, indent=2) + '\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"theme": {
|
||||
"url": "https://demo.actualbudget.org/static/js/theme.EvD-JhP6.chunk.js.map",
|
||||
"map_sha256": "ceef8194eaca339e55f35cf2decf7ceffa121c7744ee1fffd976a07d39477b32",
|
||||
"reported_source": "../../../../component-library/src/theme.ts",
|
||||
"source_sha256": "05307dc6f927705a8c76e43579c910bf6415ce28b375eea19251dc51dd2b1726"
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"source": "locked installed node_modules/ms",
|
||||
"files": {
|
||||
"index.js": "e5f0b6a946a9b2b356a28557728410717df54ea2f599edb619f9839df6b7b0e9",
|
||||
"package.json": "1a6b4d9739790c0b94ab96c8cc0507e281c164c311ff4fbf5e57fb8d26290b40"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
{
|
||||
"method": "tiktoken0.12.0 cl100k_base compactUnicodeJSON argv and full parsedstdoutEvidence plusliteralprogressstderr; no MCPcatalog/chat/modelbilling",
|
||||
"runtime_hashes": {
|
||||
"scripts/rea.mjs": "11120fdc741c2eeca9ce903de5fd18cd1c7c3fffa15ae2beccca750f161d9ee3",
|
||||
"dist/cli.js": "acd23e0f186188c99fb905cba36f81e11f92d7f280acd684a9ee93ccc75eb9ce",
|
||||
"dist/domain/javascript/javascriptSemanticResourceLimits.js": "567ecef0cdb593c75ac156075a43c0e3fc8fb6b5ade3991614f58feb256d075c",
|
||||
"dist/domain/javascript/javascriptSemanticPrecision.js": "56cec74b6fbe0c2dd6e4fb058811f35635d63ae3235f74d2b408e1b7c6dcc366",
|
||||
"dist/domain/javascript/javascriptSemanticAnalysis.js": "c21da46e866ce49f69a45e0b5ab923fcb8eb6746a50e8f6c8b77ee61d0ca0bbd",
|
||||
"dist/javascript/analysis/JavaScriptAnalysisWorker.js": "132f199041291d70ae304474a86143fb4bba3e8df4d0114607bce0f5c3f2fe61",
|
||||
"dist/cli-runtime/cli.js": "b24d2408a52483a8e9b93435110c243deb53bb7ef94757c802a0c6246d8df20c",
|
||||
"dist/cli-runtime/cliBuiltinArguments.js": "7b0c81902f568cdd7f8003b7baadcb4f493828b1e472ee613940168213e15090",
|
||||
"./dist/composition/javascriptAnalysisWorkerProcess.js": "ab927845ccfe917549198b5438d4d206c3ecfb087b0e79e431519bbf4ef1f9fd"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"name": "r1-ms-default",
|
||||
"task": "ms",
|
||||
"budget": "default",
|
||||
"verified_facts": 1,
|
||||
"unknowns": 133,
|
||||
"wall_s": 0.9129875000071479,
|
||||
"cpu_s": 1.239214,
|
||||
"host_group_rss_bytes": 344621056,
|
||||
"tokens": {
|
||||
"arguments": 67,
|
||||
"reply": 233873,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 624004,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
},
|
||||
{
|
||||
"name": "r1-ms-double",
|
||||
"task": "ms",
|
||||
"budget": "double",
|
||||
"verified_facts": 1,
|
||||
"unknowns": 133,
|
||||
"wall_s": 0.9050752910115989,
|
||||
"cpu_s": 1.2302469999999999,
|
||||
"host_group_rss_bytes": 348717056,
|
||||
"tokens": {
|
||||
"arguments": 81,
|
||||
"reply": 233914,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 624244,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
},
|
||||
{
|
||||
"name": "r1-theme-default",
|
||||
"task": "theme",
|
||||
"budget": "default",
|
||||
"verified_facts": 237,
|
||||
"unknowns": 0,
|
||||
"wall_s": 0.9374774580064695,
|
||||
"cpu_s": 1.221098,
|
||||
"host_group_rss_bytes": 346652672,
|
||||
"tokens": {
|
||||
"arguments": 67,
|
||||
"reply": 258656,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 733950,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
},
|
||||
{
|
||||
"name": "r1-theme-double",
|
||||
"task": "theme",
|
||||
"budget": "double",
|
||||
"verified_facts": 237,
|
||||
"unknowns": 0,
|
||||
"wall_s": 0.9283738329977496,
|
||||
"cpu_s": 1.236008,
|
||||
"host_group_rss_bytes": 346423296,
|
||||
"tokens": {
|
||||
"arguments": 81,
|
||||
"reply": 258721,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 734190,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
},
|
||||
{
|
||||
"name": "r2-ms-default",
|
||||
"task": "ms",
|
||||
"budget": "default",
|
||||
"verified_facts": 1,
|
||||
"unknowns": 133,
|
||||
"wall_s": 0.8994753339939052,
|
||||
"cpu_s": 1.211395,
|
||||
"host_group_rss_bytes": 326647808,
|
||||
"tokens": {
|
||||
"arguments": 67,
|
||||
"reply": 233873,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 624004,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
},
|
||||
{
|
||||
"name": "r2-ms-double",
|
||||
"task": "ms",
|
||||
"budget": "double",
|
||||
"verified_facts": 1,
|
||||
"unknowns": 133,
|
||||
"wall_s": 0.9041235420008888,
|
||||
"cpu_s": 1.206674,
|
||||
"host_group_rss_bytes": 326893568,
|
||||
"tokens": {
|
||||
"arguments": 81,
|
||||
"reply": 233914,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 624244,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
},
|
||||
{
|
||||
"name": "r2-theme-default",
|
||||
"task": "theme",
|
||||
"budget": "default",
|
||||
"verified_facts": 237,
|
||||
"unknowns": 0,
|
||||
"wall_s": 0.9415311659977306,
|
||||
"cpu_s": 1.231308,
|
||||
"host_group_rss_bytes": 348160000,
|
||||
"tokens": {
|
||||
"arguments": 67,
|
||||
"reply": 258656,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 733950,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
},
|
||||
{
|
||||
"name": "r2-theme-double",
|
||||
"task": "theme",
|
||||
"budget": "double",
|
||||
"verified_facts": 237,
|
||||
"unknowns": 0,
|
||||
"wall_s": 0.9384306669962825,
|
||||
"cpu_s": 1.228734,
|
||||
"host_group_rss_bytes": 350060544,
|
||||
"tokens": {
|
||||
"arguments": 81,
|
||||
"reply": 258721,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 734190,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
},
|
||||
{
|
||||
"name": "r3-ms-default",
|
||||
"task": "ms",
|
||||
"budget": "default",
|
||||
"verified_facts": 1,
|
||||
"unknowns": 133,
|
||||
"wall_s": 0.8975688329956029,
|
||||
"cpu_s": 1.2376939999999998,
|
||||
"host_group_rss_bytes": 337887232,
|
||||
"tokens": {
|
||||
"arguments": 67,
|
||||
"reply": 233873,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 624004,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
},
|
||||
{
|
||||
"name": "r3-ms-double",
|
||||
"task": "ms",
|
||||
"budget": "double",
|
||||
"verified_facts": 1,
|
||||
"unknowns": 133,
|
||||
"wall_s": 0.9211560420080787,
|
||||
"cpu_s": 1.22497,
|
||||
"host_group_rss_bytes": 346587136,
|
||||
"tokens": {
|
||||
"arguments": 81,
|
||||
"reply": 233914,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 624244,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
},
|
||||
{
|
||||
"name": "r3-theme-default",
|
||||
"task": "theme",
|
||||
"budget": "default",
|
||||
"verified_facts": 237,
|
||||
"unknowns": 0,
|
||||
"wall_s": 0.9006596249964787,
|
||||
"cpu_s": 1.225968,
|
||||
"host_group_rss_bytes": 340787200,
|
||||
"tokens": {
|
||||
"arguments": 67,
|
||||
"reply": 258656,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 733950,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
},
|
||||
{
|
||||
"name": "r3-theme-double",
|
||||
"task": "theme",
|
||||
"budget": "double",
|
||||
"verified_facts": 237,
|
||||
"unknowns": 0,
|
||||
"wall_s": 0.9200200000050245,
|
||||
"cpu_s": 1.2206029999999999,
|
||||
"host_group_rss_bytes": 346898432,
|
||||
"tokens": {
|
||||
"arguments": 81,
|
||||
"reply": 258721,
|
||||
"progress_stderr": 673
|
||||
},
|
||||
"reply_bytes": 734190,
|
||||
"exit_code": 0,
|
||||
"cleanup": "group_empty",
|
||||
"stop_reason": null
|
||||
}
|
||||
],
|
||||
"medians": [
|
||||
{
|
||||
"task": "theme",
|
||||
"budget": "default",
|
||||
"trials": 3,
|
||||
"facts": 237,
|
||||
"unknowns": 0,
|
||||
"wall_s": 0.9374774580064695,
|
||||
"cpu_s": 1.225968,
|
||||
"host_group_rss_mib": 330.59375,
|
||||
"tokens": 259396,
|
||||
"reply_bytes": 733950
|
||||
},
|
||||
{
|
||||
"task": "theme",
|
||||
"budget": "double",
|
||||
"trials": 3,
|
||||
"facts": 237,
|
||||
"unknowns": 0,
|
||||
"wall_s": 0.9283738329977496,
|
||||
"cpu_s": 1.228734,
|
||||
"host_group_rss_mib": 330.828125,
|
||||
"tokens": 259475,
|
||||
"reply_bytes": 734190
|
||||
},
|
||||
{
|
||||
"task": "ms",
|
||||
"budget": "default",
|
||||
"trials": 3,
|
||||
"facts": 1,
|
||||
"unknowns": 133,
|
||||
"wall_s": 0.8994753339939052,
|
||||
"cpu_s": 1.2376939999999998,
|
||||
"host_group_rss_mib": 322.234375,
|
||||
"tokens": 234613,
|
||||
"reply_bytes": 624004
|
||||
},
|
||||
{
|
||||
"task": "ms",
|
||||
"budget": "double",
|
||||
"trials": 3,
|
||||
"facts": 1,
|
||||
"unknowns": 133,
|
||||
"wall_s": 0.9050752910115989,
|
||||
"cpu_s": 1.22497,
|
||||
"host_group_rss_mib": 330.53125,
|
||||
"tokens": 234668,
|
||||
"reply_bytes": 624244
|
||||
}
|
||||
],
|
||||
"baseline": {
|
||||
"head": "e2a2ede5440df396e44b3406ef511a31a680290e",
|
||||
"working_tree": "uncommitted runtime; relevant compiled entrypoint/owner hashes retained; HEAD alone does not identify it",
|
||||
"original_task_sha256": "a17526f4b4325139896488b1d64106e5f955f7a5f998a062cb6d319425136782",
|
||||
"original_oracle_sha256": "1f4070c8814a1cd45b5ab205efd9cbf797f4b82aff195abb41877f79642c5388"
|
||||
},
|
||||
"measurement_scope": {
|
||||
"observer": "sampled simultaneous process-group RSS; not a kernel hard cap",
|
||||
"excluded_memory": "isolated JavaScript worker launches detached into another process group; its peak memory is unmeasured",
|
||||
"cpu": "wait4 waited-subtree user+system; sampler and unwaited descendants excluded",
|
||||
"cache": "fresh CLI/worker per trial; OS caches not flushed; fixed default-then-double ordering",
|
||||
"node": "24.22.1",
|
||||
"host": "macOS26.3 arm64",
|
||||
"heap_mib": "CLI and isolated worker both held512 via NODE_OPTIONS and --max-heap-mb",
|
||||
"deadline_seconds": 90,
|
||||
"group_early_stop_mib": 1536,
|
||||
"calls_per_task": 1,
|
||||
"retries": 0
|
||||
},
|
||||
"unknown_equivalence": {
|
||||
"theme": {
|
||||
"unknown_meaning_sha256": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
|
||||
"equivalent_trials": 6
|
||||
},
|
||||
"ms": {
|
||||
"unknown_meaning_sha256": "9868f30b3b2ffc97cea3b26908a54673ff0137970a41c8c6ab95356ae48a9f19",
|
||||
"equivalent_trials": 6
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
"""Verify pinned inputs and prepare inert application directories and an oracle."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
|
||||
|
||||
def sha256(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def theme_values(source):
|
||||
prefix, suffix = 'export const theme = {', '};\n'
|
||||
if not source.startswith(prefix) or not source.endswith(suffix):
|
||||
raise ValueError('Expected the pinned literal-only theme declaration')
|
||||
body = source[len(prefix):-len(suffix)]
|
||||
pair = re.compile(r"\s*([A-Za-z_]\w*)\s*:\s*'([^'\\]*)'\s*,")
|
||||
values = {}
|
||||
position = 0
|
||||
while body[position:].strip():
|
||||
item = pair.match(body, position)
|
||||
if item is None or item[1] in values:
|
||||
raise ValueError('Oracle only accepts distinct literal CSS property declarations')
|
||||
values[item[1]] = item[2]
|
||||
position = item.end()
|
||||
if len(values) != 237:
|
||||
raise ValueError('The pinned source must declare 237 theme properties')
|
||||
return values
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('repo', type=Path)
|
||||
parser.add_argument('source_map', type=Path)
|
||||
parser.add_argument('output', type=Path)
|
||||
options = parser.parse_args()
|
||||
manifest = json.loads((Path(__file__).parent / 'manifest.json').read_text())
|
||||
pinned = manifest['theme']
|
||||
if sha256(options.source_map) != pinned['map_sha256']:
|
||||
parser.error('Source-map digest does not match the pinned artifact')
|
||||
document = json.loads(options.source_map.read_text())
|
||||
matching = [text for name, text in zip(document['sources'], document['sourcesContent'])
|
||||
if name == pinned['reported_source']]
|
||||
if len(matching) != 1 or matching[0] is None:
|
||||
parser.error('Expected one embedded original theme source')
|
||||
source = matching[0]
|
||||
if hashlib.sha256(source.encode()).hexdigest() != pinned['source_sha256']:
|
||||
parser.error('Embedded source digest mismatch')
|
||||
theme = options.output / 'cases/theme'
|
||||
control = options.output / 'cases/ms'
|
||||
theme.mkdir(parents=True, exist_ok=True)
|
||||
control.mkdir(parents=True, exist_ok=True)
|
||||
(theme / 'theme.ts').write_text(source)
|
||||
(theme / 'package.json').write_text(json.dumps({
|
||||
'name': 'rea-captured-theme-calibration', 'type': 'module', 'main': 'theme.ts',
|
||||
}))
|
||||
for name, digest in manifest['ms']['files'].items():
|
||||
path = options.repo / 'node_modules/ms' / name
|
||||
if sha256(path) != digest:
|
||||
parser.error(f'Installed ms input digest mismatch: {name}')
|
||||
shutil.copyfile(path, control / name)
|
||||
oracle = {
|
||||
'theme': {'source_file': 'theme.ts', 'source_sha256': pinned['source_sha256'],
|
||||
'expected': theme_values(source)},
|
||||
'ms': {'source_file': 'index.js',
|
||||
'source_sha256': manifest['ms']['files']['index.js'], 'expected': {'s': 1000}},
|
||||
}
|
||||
options.output.mkdir(parents=True, exist_ok=True)
|
||||
(options.output / 'oracle.json').write_text(json.dumps(oracle, indent=2) + '\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,68 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { createHash } from "node:crypto";
|
||||
import { parse } from "@babel/parser";
|
||||
const [output, ...files] = process.argv.slice(2);
|
||||
const rows = [];
|
||||
for (const file of files) {
|
||||
const text = await readFile(file, "utf8");
|
||||
const ast = parse(text, { sourceType: "unambiguous" });
|
||||
const strings = [];
|
||||
let nodes = 0;
|
||||
const stack = [ast];
|
||||
while (stack.length) {
|
||||
const n = stack.pop();
|
||||
if (!n || typeof n !== "object") continue;
|
||||
if (Array.isArray(n)) {
|
||||
stack.push(...n);
|
||||
continue;
|
||||
}
|
||||
if (typeof n.type === "string") nodes++;
|
||||
if (n.type === "StringLiteral") {
|
||||
let oracle = false;
|
||||
try {
|
||||
oracle = JSON.parse(text.slice(n.start, n.end)) === n.value;
|
||||
} catch {}
|
||||
strings.push({
|
||||
start: n.start,
|
||||
end: n.end,
|
||||
units: n.value.length,
|
||||
jsonBytes: Buffer.byteLength(JSON.stringify(n.value)),
|
||||
oracle,
|
||||
});
|
||||
}
|
||||
if (
|
||||
n.type === "TemplateLiteral" &&
|
||||
n.expressions.length === 0 &&
|
||||
n.quasis[0].value.cooked !== null
|
||||
) {
|
||||
const value = n.quasis[0].value.cooked;
|
||||
const raw = text.slice(n.start + 1, n.end - 1);
|
||||
strings.push({
|
||||
start: n.start,
|
||||
end: n.end,
|
||||
units: value.length,
|
||||
jsonBytes: Buffer.byteLength(JSON.stringify(value)),
|
||||
oracle: !raw.includes("\\") && raw === value,
|
||||
template: true,
|
||||
});
|
||||
}
|
||||
for (const [k, v] of Object.entries(n))
|
||||
if (
|
||||
!["loc", "extra", "comments", "tokens"].includes(k) &&
|
||||
v &&
|
||||
typeof v === "object"
|
||||
)
|
||||
stack.push(v);
|
||||
}
|
||||
strings.sort((a, b) => b.units - a.units);
|
||||
rows.push({
|
||||
file,
|
||||
bytes: Buffer.byteLength(text),
|
||||
sha256: createHash("sha256").update(text).digest("hex"),
|
||||
nodes,
|
||||
strings: strings.length,
|
||||
largest: strings.slice(0, 5),
|
||||
});
|
||||
}
|
||||
await writeFile(output, JSON.stringify(rows, null, 2));
|
||||
console.log(JSON.stringify(rows));
|
||||
@@ -0,0 +1,192 @@
|
||||
[
|
||||
{
|
||||
"file": "/tmp/rea-source-map-calibration/theme.EvD-JhP6.chunk.js",
|
||||
"bytes": 29718,
|
||||
"sha256": "10ecf4c66ae103222a7c146ad9b945dc83e78b6c78ce05021296e3c2f799bd5b",
|
||||
"nodes": 4022,
|
||||
"strings": 349,
|
||||
"largest": [
|
||||
{
|
||||
"start": 1434,
|
||||
"end": 1589,
|
||||
"units": 152,
|
||||
"jsonBytes": 155,
|
||||
"oracle": true
|
||||
},
|
||||
{
|
||||
"start": 3123,
|
||||
"end": 3227,
|
||||
"units": 102,
|
||||
"jsonBytes": 104,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
},
|
||||
{
|
||||
"start": 7255,
|
||||
"end": 7330,
|
||||
"units": 73,
|
||||
"jsonBytes": 75,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
},
|
||||
{
|
||||
"start": 18225,
|
||||
"end": 18297,
|
||||
"units": 70,
|
||||
"jsonBytes": 72,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
},
|
||||
{
|
||||
"start": 10083,
|
||||
"end": 10154,
|
||||
"units": 69,
|
||||
"jsonBytes": 71,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "/tmp/rea-source-map-calibration/index.CMrE7aQb.js",
|
||||
"bytes": 2078745,
|
||||
"sha256": "937f792d1849b69a6e0741234428b22b4884ec0157ea31dd6949dd1bacc46456",
|
||||
"nodes": 460754,
|
||||
"strings": 15897,
|
||||
"largest": [
|
||||
{
|
||||
"start": 78956,
|
||||
"end": 79894,
|
||||
"units": 936,
|
||||
"jsonBytes": 938,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
},
|
||||
{
|
||||
"start": 104891,
|
||||
"end": 105550,
|
||||
"units": 657,
|
||||
"jsonBytes": 659,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
},
|
||||
{
|
||||
"start": 1752523,
|
||||
"end": 1753169,
|
||||
"units": 644,
|
||||
"jsonBytes": 662,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
},
|
||||
{
|
||||
"start": 1751921,
|
||||
"end": 1752324,
|
||||
"units": 401,
|
||||
"jsonBytes": 413,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
},
|
||||
{
|
||||
"start": 583954,
|
||||
"end": 584343,
|
||||
"units": 387,
|
||||
"jsonBytes": 391,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "/tmp/rea-source-map-calibration/DateSelect.BVYaCNUe.chunk.js",
|
||||
"bytes": 2191517,
|
||||
"sha256": "9e45139dd0b510ad7014188a5e9d11b307c2ac15dfdc761050388e76e94759bf",
|
||||
"nodes": 363578,
|
||||
"strings": 29000,
|
||||
"largest": [
|
||||
{
|
||||
"start": 34808,
|
||||
"end": 47930,
|
||||
"units": 13120,
|
||||
"jsonBytes": 13122,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
},
|
||||
{
|
||||
"start": 21787,
|
||||
"end": 34790,
|
||||
"units": 13001,
|
||||
"jsonBytes": 13003,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
},
|
||||
{
|
||||
"start": 8827,
|
||||
"end": 21772,
|
||||
"units": 12943,
|
||||
"jsonBytes": 12945,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
},
|
||||
{
|
||||
"start": 47947,
|
||||
"end": 50426,
|
||||
"units": 2477,
|
||||
"jsonBytes": 2479,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
},
|
||||
{
|
||||
"start": 50458,
|
||||
"end": 52394,
|
||||
"units": 1934,
|
||||
"jsonBytes": 1936,
|
||||
"oracle": true,
|
||||
"template": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "node_modules/ms/index.js",
|
||||
"bytes": 3024,
|
||||
"sha256": "e5f0b6a946a9b2b356a28557728410717df54ea2f599edb619f9839df6b7b0e9",
|
||||
"nodes": 427,
|
||||
"strings": 48,
|
||||
"largest": [
|
||||
{
|
||||
"start": 702,
|
||||
"end": 757,
|
||||
"units": 53,
|
||||
"jsonBytes": 55,
|
||||
"oracle": false
|
||||
},
|
||||
{
|
||||
"start": 1851,
|
||||
"end": 1865,
|
||||
"units": 12,
|
||||
"jsonBytes": 14,
|
||||
"oracle": false
|
||||
},
|
||||
{
|
||||
"start": 1876,
|
||||
"end": 1889,
|
||||
"units": 11,
|
||||
"jsonBytes": 13,
|
||||
"oracle": false
|
||||
},
|
||||
{
|
||||
"start": 1745,
|
||||
"end": 1754,
|
||||
"units": 7,
|
||||
"jsonBytes": 9,
|
||||
"oracle": false
|
||||
},
|
||||
{
|
||||
"start": 1639,
|
||||
"end": 1648,
|
||||
"units": 7,
|
||||
"jsonBytes": 9,
|
||||
"oracle": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Run one complete CLI task and independently check the required source facts."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('repo', type=Path)
|
||||
parser.add_argument('node')
|
||||
parser.add_argument('inputs', type=Path)
|
||||
parser.add_argument('case', choices=['theme', 'ms'])
|
||||
parser.add_argument('budget', choices=['default', 'double'])
|
||||
parser.add_argument('output', type=Path)
|
||||
options = parser.parse_args()
|
||||
oracle = json.loads((options.inputs / 'oracle.json').read_text())[options.case]
|
||||
target = options.inputs / 'cases' / options.case
|
||||
source = target / oracle['source_file']
|
||||
if hashlib.sha256(source.read_bytes()).hexdigest() != oracle['source_sha256']:
|
||||
raise ValueError('Selected source digest does not match the oracle')
|
||||
argv = [options.node, str(options.repo / 'scripts/rea.mjs'),
|
||||
'analyze-javascript-application', str(target), '--max-heap-mb', '512',
|
||||
'--format', 'json']
|
||||
if options.budget == 'double':
|
||||
argv += ['--semantic-precision', '{"primitive_bytes":2097152}']
|
||||
environment = {**os.environ, 'NODE_OPTIONS': '--max-old-space-size=512'}
|
||||
result = subprocess.run(argv, cwd=options.repo, env=environment, capture_output=True,
|
||||
text=True, timeout=45)
|
||||
options.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
options.output.write_text(json.dumps({
|
||||
'argv': argv, 'stdout': result.stdout, 'stderr': result.stderr,
|
||||
'exit_code': result.returncode,
|
||||
}) + '\n')
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f'CLI failed: {result.stderr[:500]}')
|
||||
evidence = json.loads(result.stdout)
|
||||
graph = evidence['normalized_result']['semantic_graph']
|
||||
nodes = {node['node_id']: node for node in graph['nodes']}
|
||||
if any(node['identity']['artifact_sha256'] != oracle['source_sha256']
|
||||
for node in nodes.values()
|
||||
if node['identity']['module_path'] == oracle['source_file']):
|
||||
raise ValueError('Selected graph source identity differs from the oracle')
|
||||
facts = {}
|
||||
for edge in graph['relations']:
|
||||
if edge['relation'] != 'defines':
|
||||
continue
|
||||
left, right = nodes[edge['source_node_id']], nodes[edge['target_node_id']]
|
||||
if left['kind'] != 'literal':
|
||||
continue
|
||||
if options.case == 'theme' and right['kind'] == 'property-slot':
|
||||
facts[right['properties']['name']] = left['properties']['value']
|
||||
if options.case == 'ms' and right['kind'] == 'binding' and right['label'] == 's':
|
||||
facts['s'] = left['properties']['value']
|
||||
if facts != oracle['expected']:
|
||||
raise ValueError('Required source facts differ from the independent oracle')
|
||||
if any('primitive-bytes' in json.dumps(unknown) for unknown in graph['unknowns']):
|
||||
raise ValueError('Selected byte allowance unexpectedly limited the task')
|
||||
print(json.dumps({
|
||||
'case': options.case, 'budget': options.budget, 'verified_facts': len(facts),
|
||||
'source_sha256': oracle['source_sha256'],
|
||||
'reply_bytes': len(result.stdout.encode()), 'unknowns': len(graph['unknowns']),
|
||||
}))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,199 @@
|
||||
"""Validate and summarize bounded public source-location task receipts.
|
||||
|
||||
Requires tiktoken 0.12.0. Full replies stay in the caller-selected measurement
|
||||
directory; the output retains only identities, hashes, numeric measurements and
|
||||
per-call serialized JSON-RPC token/byte estimates.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import statistics
|
||||
|
||||
import tiktoken
|
||||
|
||||
|
||||
def compact(value, *, ordered=False):
|
||||
return json.dumps(value, ensure_ascii=False, separators=(',', ':'), sort_keys=ordered)
|
||||
|
||||
|
||||
def sha256(path):
|
||||
digest = hashlib.sha256()
|
||||
with path.open('rb') as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b''):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('measurement_dir', type=Path)
|
||||
parser.add_argument('--repo-root', type=Path, required=True)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
repo = args.repo_root.resolve()
|
||||
assert tiktoken.__version__ == '0.12.0', 'Use the recorded tokenizer version'
|
||||
encoder = tiktoken.get_encoding('cl100k_base')
|
||||
token_cache = {}
|
||||
|
||||
def tokens(value, *, line=False):
|
||||
document = compact(value) + ('\n' if line else '')
|
||||
digest = hashlib.sha256(document.encode()).hexdigest()
|
||||
if digest not in token_cache:
|
||||
token_cache[digest] = len(encoder.encode(document, disallowed_special=()))
|
||||
return token_cache[digest]
|
||||
|
||||
pins_path = repo / 'docs/verification/source-map-budgets-2026-11-09/artifacts.json'
|
||||
artifacts = json.loads(pins_path.read_text())
|
||||
records, semantic_answers, evidence_ids = [], {}, {}
|
||||
definitions = None
|
||||
input_only_definitions = None
|
||||
artifact_cache = {}
|
||||
receipts = sorted(args.measurement_dir.glob('r*-*.metrics.json'), key=lambda p: p.stat().st_mtime_ns)
|
||||
assert len(receipts) == 18, f'Expected 18 final receipts, found {len(receipts)}'
|
||||
for path in receipts:
|
||||
name = path.name.removesuffix('.metrics.json')
|
||||
selected = re.fullmatch(r'r([123])-(theme|index|DateSelect)-(complete|summary)', name)
|
||||
assert selected, name
|
||||
trial, case, detail = int(selected[1]), selected[2], selected[3]
|
||||
metric = json.loads(path.read_text())
|
||||
trace_path = path.with_name(name + '.replies.json')
|
||||
trace = json.loads(trace_path.read_text())
|
||||
assert metric['exit_code'] == 0 and metric['cleanup'] == 'group_empty'
|
||||
assert metric['stop_reason'] is None and metric['observer_error'] is None
|
||||
assert metric['signal_errors'] == [] and metric['remaining_group_pids'] == []
|
||||
assert trace['config']['select'] == case + '.' and trace['config']['detail'] == detail
|
||||
answer = trace['answer']
|
||||
artifact = next(item for item in artifacts if case + '.' in item['path'])
|
||||
assert answer['source_map_sha256'] == artifact['sha256']
|
||||
assert answer['lookup']['requested'] == artifact['point']
|
||||
assert answer['lookup']['matched_generated_position'] == artifact['point']
|
||||
assert len(answer['matches']) == len(artifact['expected_matches'])
|
||||
if case not in artifact_cache:
|
||||
raw_path = Path(trace['config']['artifactDir']) / artifact['path']
|
||||
assert sha256(raw_path) == artifact['sha256']
|
||||
artifact_cache[case] = json.loads(raw_path.read_text())
|
||||
raw = artifact_cache[case]
|
||||
for index, (actual, expected) in enumerate(zip(answer['matches'], artifact['expected_matches'])):
|
||||
assert actual['state'] == ('generated-only' if expected.get('generated_only') else 'mapped')
|
||||
if expected.get('generated_only'):
|
||||
continue
|
||||
assert actual['match_index'] == index
|
||||
assert actual['source_index'] == expected['source_index']
|
||||
assert actual['reported_source'] == expected['source']
|
||||
assert actual['original_position']['line'] == expected['original_line']
|
||||
assert actual['original_position']['column'] == expected['original_column']
|
||||
body = raw['sourcesContent'][expected['source_index']]
|
||||
assert hashlib.sha256(body.encode()).hexdigest() == expected['content_sha256']
|
||||
assert actual['content']['utf8_sha256'] == expected['content_sha256']
|
||||
snippet = actual['content']['snippet']
|
||||
encoded = body.encode('utf-16-le')
|
||||
start, length = snippet['offset_utf16'], snippet['length_utf16']
|
||||
assert snippet['text'] == encoded[start * 2:(start + length) * 2].decode('utf-16-le')
|
||||
assert length == 512, 'These pinned tasks require a 512-unit snippet'
|
||||
assert start <= actual['original_position']['offset'] <= start + length
|
||||
assert actual['content']['code_units'] == len(encoded) // 2
|
||||
semantic = {key: answer[key] for key in ['evidence_id', 'source_map_sha256', 'lookup', 'matches']}
|
||||
semantic_digest = hashlib.sha256(compact(semantic, ordered=True).encode()).hexdigest()
|
||||
if case in semantic_answers:
|
||||
assert semantic_digest == semantic_answers[case], f'Answer mismatch in {name}'
|
||||
assert answer['evidence_id'] == evidence_ids[case]
|
||||
else:
|
||||
semantic_answers[case], evidence_ids[case] = semantic_digest, answer['evidence_id']
|
||||
catalog = next(call['reply']['result'] for call in trace['calls'] if call['request']['method'] == 'tools/list')
|
||||
chosen_definitions = {tool: next(item for item in catalog['tools'] if item['name'] == tool) for tool in ['trace_web_source_location', 'inspect_analysis_view']}
|
||||
definition_counts = {tool: tokens(definition) for tool, definition in chosen_definitions.items()}
|
||||
input_only_counts = {tool: tokens({key: definition[key] for key in ['name', 'description', 'inputSchema']}) for tool, definition in chosen_definitions.items()}
|
||||
if definitions is None:
|
||||
definitions = definition_counts
|
||||
input_only_definitions = input_only_counts
|
||||
else:
|
||||
assert definitions == definition_counts
|
||||
assert input_only_definitions == input_only_counts
|
||||
rows, seen_catalog = [], False
|
||||
used_tools = set()
|
||||
for call in trace['calls']:
|
||||
request, reply = call['request'], call['reply']
|
||||
operation = request.get('params', {}).get('name', request['method'])
|
||||
request_count = tokens(request, line=True)
|
||||
reply_count = tokens(reply, line=True) if reply is not None else 0
|
||||
post_discovery = seen_catalog
|
||||
if request['method'] == 'tools/list':
|
||||
seen_catalog = True
|
||||
if request['method'] == 'tools/call':
|
||||
used_tools.add(operation)
|
||||
if reply is not None and request['method'] == 'tools/call' and not reply['result'].get('isError'):
|
||||
normalized = reply['result']['structuredContent']['normalized_result']
|
||||
source_report = normalized.get('source_location', normalized.get('summary', normalized))
|
||||
assert source_report['source']['sha256'] == artifact['generated_script']['sha256']
|
||||
assert source_report['source_map']['sha256'] == artifact['sha256']
|
||||
rows.append({
|
||||
'operation': operation, 'post_discovery': post_discovery,
|
||||
'request_tokens_estimated': request_count, 'reply_tokens_estimated': reply_count,
|
||||
'request_bytes_compact_json_line': len((compact(request) + '\n').encode()),
|
||||
'reply_bytes_observed_wire': call['wire_bytes'],
|
||||
'reply_bytes_reserialized_json_line': len((compact(reply) + '\n').encode()) if reply is not None else 0,
|
||||
'elapsed_ms': call['elapsed_ms'],
|
||||
'is_error': reply is not None and reply.get('result', {}).get('isError', False),
|
||||
})
|
||||
if operation == 'trace_web_source_location' and reply is not None:
|
||||
result = reply['result']
|
||||
if not result.get('isError') and detail == 'complete':
|
||||
text = result['structuredContent']['raw_result']['source_map_text']
|
||||
assert hashlib.sha256(text.encode()).hexdigest() == artifact['sha256']
|
||||
total = sum(row['request_tokens_estimated'] + row['reply_tokens_estimated'] for row in rows)
|
||||
post = sum(row['request_tokens_estimated'] + row['reply_tokens_estimated'] for row in rows if row['post_discovery'])
|
||||
assert used_tools <= chosen_definitions.keys()
|
||||
records.append({
|
||||
'name': name, 'trial': trial, 'artifact': case, 'detail': detail,
|
||||
'evidence_id': answer['evidence_id'], 'map_sha256': artifact['sha256'], 'generated_script_sha256': artifact['generated_script']['sha256'], 'answer_sha256': semantic_digest,
|
||||
'receipt_sha256': sha256(path), 'reply_trace_sha256': sha256(trace_path),
|
||||
'task_tokens_catalog_included_estimated': total, 'post_discovery_tokens_estimated': post,
|
||||
'used_tool_definitions_tokens_estimated': sum(definitions[tool] for tool in used_tools),
|
||||
'post_discovery_plus_used_definitions_tokens_estimated': post + sum(definitions[tool] for tool in used_tools),
|
||||
'hypothetical_used_input_only_definitions_tokens_estimated': sum(input_only_definitions[tool] for tool in used_tools),
|
||||
'hypothetical_post_discovery_plus_used_input_only_definitions_tokens_estimated': post + sum(input_only_definitions[tool] for tool in used_tools),
|
||||
'tool_calls': sum(call['request']['method'] == 'tools/call' for call in trace['calls']),
|
||||
'elapsed_seconds': metric['elapsed_seconds'], 'cpu_seconds_waited_subtree': metric['cpu_seconds_waited_subtree'],
|
||||
'peak_process_group_rss_bytes_sampled': metric['peak_process_group_rss_bytes_sampled'],
|
||||
'peak_process_tree_rss_bytes_sampled': metric['peak_process_tree_rss_bytes_sampled'],
|
||||
'maximum_group_members_sampled': metric['maximum_group_members_sampled'],
|
||||
'maximum_tree_members_sampled': metric['maximum_tree_members_sampled'],
|
||||
'sample_count': metric['sample_count'], 'exit_code': metric['exit_code'], 'cleanup': metric['cleanup'], 'stop_reason': metric['stop_reason'],
|
||||
'calls': rows,
|
||||
})
|
||||
groups = {}
|
||||
for record in records:
|
||||
groups.setdefault(record['artifact'] + '-' + record['detail'], []).append(record)
|
||||
fields = ['post_discovery_plus_used_definitions_tokens_estimated', 'hypothetical_used_input_only_definitions_tokens_estimated', 'hypothetical_post_discovery_plus_used_input_only_definitions_tokens_estimated', 'task_tokens_catalog_included_estimated', 'post_discovery_tokens_estimated', 'used_tool_definitions_tokens_estimated', 'tool_calls', 'elapsed_seconds', 'cpu_seconds_waited_subtree', 'peak_process_group_rss_bytes_sampled', 'peak_process_tree_rss_bytes_sampled']
|
||||
medians = {name: {'runs': len(group), **{field: statistics.median(record[field] for record in group) for field in fields}} for name, group in groups.items()}
|
||||
assert all(len(group) == 3 for group in groups.values()) and len(groups) == 6
|
||||
recorded_runtime = json.loads((args.measurement_dir / 'runtime.json').read_text())
|
||||
paths = ['scripts/verify/analysis/source-location-delivery/task.mjs', 'scripts/verify/analysis/budget-calibration-run.py', 'scripts/rea.mjs', 'dist/cli.js', 'dist/mcp/main.js', 'src/application/WebSourceLocationService.ts', 'src/domain/analysisView/webSourceLocationView.ts', 'src/application/analysisView/AnalysisViewService.ts', 'src/contracts/webScriptToolContracts.ts', 'src/server/ToolResultDelivery.ts', 'src/server/toolResult.ts', 'src/domain/sourceMapLimits.ts', 'src/server/registerWebSourceLocationTool.ts', 'src/cli/webSourceLocationCommands.ts', 'scripts/verify/analysis/source-location-delivery/aggregate.py', 'package-lock.json', 'docs/verification/source-map-budgets-2026-11-09/artifacts.json']
|
||||
hashes = {path: sha256(repo / path) for path in paths}
|
||||
assert hashes[paths[0]] == recorded_runtime['driver_sha256']
|
||||
assert hashes[paths[1]] == recorded_runtime['observer_sha256']
|
||||
if recorded_runtime.get('cli_sha256') is not None:
|
||||
assert hashes['dist/cli.js'] == recorded_runtime['cli_sha256']
|
||||
tree = [(path.relative_to(repo / 'dist').as_posix(), sha256(path)) for path in sorted((repo / 'dist').rglob('*')) if path.is_file()]
|
||||
tree_digest = hashlib.sha256(''.join(path + '\0' + digest + '\n' for path, digest in tree).encode()).hexdigest()
|
||||
output = {
|
||||
'schema': 'rea-source-location-delivery-calibration/v1',
|
||||
'token_accounting': {'library': 'tiktoken', 'version': tiktoken.__version__, 'encoding': 'cl100k_base', 'representation': 'compact Unicode JSON-RPC request/reply envelopes with trailing newline; initialized notification has no reply; estimates, not model billing', 'whole_catalog_included': 'initialize + initialized notification + tools/list + every trace/continuation/text call', 'post_discovery': 'all requests/replies after tools/list; excludes discovery only, not recovery', 'selected_definitions': 'complete serialized trace_web_source_location and inspect_analysis_view definitions counted separately; do not add them again to whole-catalog totals; client cached/deferred tool presentation may differ'},
|
||||
'runtime': {'recorded_runtime': recorded_runtime, 'sha256': hashes, 'compiled_tree': {'root': 'dist', 'files': len(tree), 'sha256': tree_digest, 'encoding': 'sorted relative UTF-8 filename + NUL + lowercase SHA256 + newline for every regular file, then SHA256'}},
|
||||
'conditions': {'host': metric['platform'], 'node': 'v24.22.1', 'runs': 'three fresh-process runs; second trial reverses variant order; OS caches not flushed; no disk-cold claim', 'task': 'all equal-position mappings and exact source/map identity plus 512 UTF-16 units around each mapped point', 'comparison': 'current complete delivery including bounded retained-view overflow recovery versus opt-in summary; not the older export-only fallback', 'profiles': {'tool_catalog': 'full', 'input_schema': 'compact', 'output_schema': 'full', 'response_budget_bytes': 10485760}, 'observer': {'interval_seconds': 0.02, 'wall_deadline_seconds': 90, 'memory_early_stop_bytes': 1536 * 1024 * 1024, 'signal_scope': 'owned process group only', 'rss': 'simultaneous ps-snapshot group/tree sums; tree includes linked-PPID codec descendants outside root PGID; can miss short peaks/exited/reparented/orphaned descendants and double-count shared pages', 'cpu': 'wait4 user+system waited subtree, excluding sampler and unwaited/orphaned descendants', 'memory_guard': 'group early stop; observational tree RSS is not signal authority or a hard/kernel peak limit'}},
|
||||
'selected_definition_tokens_estimated': definitions,
|
||||
'hypothetical_input_only_definition_tokens_estimated': input_only_definitions,
|
||||
'hypothetical_input_only_definition_projection': 'Only {name, description, inputSchema} from captured definitions; not an observed client/model transformation or a measured alternate discovery workflow',
|
||||
'observed_completion_order': [record['name'] for record in records],
|
||||
'validation': {'completed_runs': len(records), 'same_answer_and_parent_id_across_variants_and_trials': True, 'independent_artifact_oracle_and_snippets_verified': True, 'successful_complete_initial_raw_map_sha256_verified': True, 'all_owned_groups_empty': True, 'all_resource_stops': None},
|
||||
'medians': medians, 'records': records,
|
||||
}
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(json.dumps(output, ensure_ascii=False, indent=2) + '\n')
|
||||
print(json.dumps({'medians': medians, 'selected_definitions': definitions, 'compiled_tree': output['runtime']['compiled_tree']}, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,426 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { createHash } from "node:crypto";
|
||||
import assert from "node:assert/strict";
|
||||
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
||||
|
||||
// Acquire artifactDir with docs/verification/source-map-budgets-2026-11-09/acquire.py.
|
||||
// This driver runs the public compiled stdio workflow; no target application is executed.
|
||||
const [rootArgument, configText, ...extra] = process.argv.slice(2);
|
||||
assert(
|
||||
rootArgument && configText && extra.length === 0,
|
||||
"Usage: node task.mjs REPO_ROOT CONFIG_JSON",
|
||||
);
|
||||
const root = resolve(rootArgument);
|
||||
const config = JSON.parse(configText);
|
||||
assert(
|
||||
config && typeof config === "object" && !Array.isArray(config),
|
||||
"CONFIG_JSON must be an object",
|
||||
);
|
||||
const allowed = new Set([
|
||||
"artifactDir",
|
||||
"select",
|
||||
"detail",
|
||||
"resultPath",
|
||||
"exportPath",
|
||||
"maxResponseBytes",
|
||||
]);
|
||||
assert(
|
||||
Object.keys(config).every((key) => allowed.has(key)),
|
||||
"Unknown task configuration field",
|
||||
);
|
||||
assert(
|
||||
typeof config.artifactDir === "string" && isAbsolute(config.artifactDir),
|
||||
"artifactDir must be an absolute acquired-artifact directory",
|
||||
);
|
||||
assert(
|
||||
typeof config.select === "string" &&
|
||||
config.select.length > 0 &&
|
||||
config.select.length <= 256,
|
||||
"select must be a nonempty artifact filename selector, at most 256 characters",
|
||||
);
|
||||
assert(
|
||||
config.detail === "complete" || config.detail === "summary",
|
||||
"detail must be complete or summary",
|
||||
);
|
||||
for (const key of ["resultPath", "exportPath"]) {
|
||||
assert(
|
||||
config[key] === undefined ||
|
||||
(typeof config[key] === "string" && isAbsolute(config[key])),
|
||||
`${key} must be an absolute path when selected`,
|
||||
);
|
||||
}
|
||||
const MAX_LINE_BYTES = 32 * 1024 * 1024;
|
||||
const RPC_TIMEOUT_MS = 45000;
|
||||
assert(
|
||||
config.maxResponseBytes === undefined ||
|
||||
(Number.isSafeInteger(config.maxResponseBytes) &&
|
||||
config.maxResponseBytes >= 10 * 1024 * 1024 &&
|
||||
config.maxResponseBytes <= MAX_LINE_BYTES),
|
||||
"maxResponseBytes must fit the driver receive guard: integer 10..32 MiB",
|
||||
);
|
||||
const sources = JSON.parse(
|
||||
await readFile(join(config.artifactDir, "artifacts.json"), "utf8"),
|
||||
);
|
||||
assert(
|
||||
Array.isArray(sources),
|
||||
"artifacts.json must be the retained artifact list",
|
||||
);
|
||||
const selected = sources.filter(
|
||||
(source) =>
|
||||
typeof source.path === "string" && source.path.includes(config.select),
|
||||
);
|
||||
assert.equal(
|
||||
selected.length,
|
||||
1,
|
||||
"select must identify exactly one pinned artifact",
|
||||
);
|
||||
const source = selected[0];
|
||||
assert(
|
||||
source.generated_script,
|
||||
"Selected point task requires an acquired generated script and analyst manifest",
|
||||
);
|
||||
const localFile = (path) => {
|
||||
assert(
|
||||
typeof path === "string" && !isAbsolute(path),
|
||||
"Artifact manifest paths must be relative",
|
||||
);
|
||||
const resolved = resolve(config.artifactDir, path);
|
||||
assert(
|
||||
!relative(config.artifactDir, resolved).startsWith(".." + sep) &&
|
||||
resolved !== resolve(config.artifactDir, ".."),
|
||||
"Artifact path must stay within artifactDir",
|
||||
);
|
||||
return resolved;
|
||||
};
|
||||
const mapPath = localFile(source.path);
|
||||
const bytes = await readFile(mapPath);
|
||||
assert.equal(bytes.length, source.bytes);
|
||||
assert.equal(createHash("sha256").update(bytes).digest("hex"), source.sha256);
|
||||
const artifact = JSON.parse(bytes.toString("utf8"));
|
||||
const manifestPath = localFile(source.generated_script.path + ".manifest.json");
|
||||
const child = spawn(process.execPath, [join(root, "scripts/rea.mjs"), "mcp"], {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
REA_MCP_TOOL_CATALOG_PROFILE: "full",
|
||||
REA_MCP_INPUT_SCHEMA_PROFILE: "compact",
|
||||
REA_MCP_OUTPUT_SCHEMA_PROFILE: "full",
|
||||
...(config.maxResponseBytes === undefined
|
||||
? {}
|
||||
: { REA_MCP_MAX_RESPONSE_BYTES: String(config.maxResponseBytes) }),
|
||||
HOPPER_LAUNCHER_PATH: "/rea-unconfigured-deep-provider/hopper",
|
||||
GHIDRA_INSTALL_DIR: process.execPath,
|
||||
},
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
let buffer = "",
|
||||
sequence = 0,
|
||||
stderr = "";
|
||||
const pending = new Map(),
|
||||
calls = [];
|
||||
const failPending = (error) => {
|
||||
for (const request of pending.values()) request.reject(error);
|
||||
pending.clear();
|
||||
};
|
||||
child.stdout.setEncoding("utf8");
|
||||
child.stderr.setEncoding("utf8");
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr = (stderr + chunk).slice(-8192);
|
||||
});
|
||||
child.once("error", failPending);
|
||||
child.once("exit", (code, signal) =>
|
||||
failPending(
|
||||
new Error(
|
||||
`MCP server exited (${String(code)}, ${String(signal)}): ${stderr}`,
|
||||
),
|
||||
),
|
||||
);
|
||||
child.stdin.on("error", failPending);
|
||||
child.stdout.on("data", (chunk) => {
|
||||
try {
|
||||
buffer += chunk;
|
||||
assert(
|
||||
Buffer.byteLength(buffer) < MAX_LINE_BYTES,
|
||||
"32 MiB RPC line guard exceeded",
|
||||
);
|
||||
let end;
|
||||
while ((end = buffer.indexOf("\n")) >= 0) {
|
||||
const line = buffer.slice(0, end);
|
||||
buffer = buffer.slice(end + 1);
|
||||
if (!line) continue;
|
||||
const payload = JSON.parse(line),
|
||||
request = pending.get(payload.id);
|
||||
if (request) {
|
||||
pending.delete(payload.id);
|
||||
request.resolve({ payload, wireBytes: Buffer.byteLength(line) + 1 });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
failPending(error);
|
||||
}
|
||||
});
|
||||
const rpc = async (method, params = {}) => {
|
||||
const request = { jsonrpc: "2.0", id: ++sequence, method, params },
|
||||
start = performance.now();
|
||||
const waiting = new Promise((resolveReply, rejectReply) => {
|
||||
const timer = setTimeout(() => {
|
||||
pending.delete(request.id);
|
||||
rejectReply(new Error(`RPC timeout ${method}: ${stderr}`));
|
||||
}, RPC_TIMEOUT_MS);
|
||||
pending.set(request.id, {
|
||||
resolve: (value) => {
|
||||
clearTimeout(timer);
|
||||
resolveReply(value);
|
||||
},
|
||||
reject: (error) => {
|
||||
clearTimeout(timer);
|
||||
rejectReply(error);
|
||||
},
|
||||
});
|
||||
});
|
||||
child.stdin.write(JSON.stringify(request) + "\n");
|
||||
const { payload, wireBytes } = await waiting;
|
||||
assert(!payload.error, JSON.stringify(payload.error));
|
||||
calls.push({
|
||||
request,
|
||||
reply: payload,
|
||||
wire_bytes: wireBytes,
|
||||
elapsed_ms: performance.now() - start,
|
||||
});
|
||||
return payload.result;
|
||||
};
|
||||
const errorOf = (r) =>
|
||||
r.isError
|
||||
? JSON.parse(r.content.find((c) => c.type === "text").text).error
|
||||
: undefined;
|
||||
const contentOf = (r) => {
|
||||
assert(!r.isError, JSON.stringify(errorOf(r)));
|
||||
return r.structuredContent;
|
||||
};
|
||||
try {
|
||||
await rpc("initialize", {
|
||||
protocolVersion: "2025-03-26",
|
||||
capabilities: {},
|
||||
clientInfo: { name: "source-location-completed-task", version: "1" },
|
||||
});
|
||||
const initialized = { jsonrpc: "2.0", method: "notifications/initialized" };
|
||||
child.stdin.write(JSON.stringify(initialized) + "\n");
|
||||
calls.push({
|
||||
request: initialized,
|
||||
reply: null,
|
||||
wire_bytes: 0,
|
||||
elapsed_ms: 0,
|
||||
});
|
||||
const catalog = await rpc("tools/list");
|
||||
const definition = catalog.tools.find(
|
||||
(t) => t.name === "trace_web_source_location",
|
||||
);
|
||||
assert(definition);
|
||||
const args = {
|
||||
manifest_path: manifestPath,
|
||||
script_index: 0,
|
||||
source_map: { path: mapPath, url: source.url },
|
||||
generated_position: source.point,
|
||||
...(config.detail === "summary" ? { detail: "summary" } : {}),
|
||||
};
|
||||
const initial = await rpc("tools/call", {
|
||||
name: "trace_web_source_location",
|
||||
arguments: args,
|
||||
});
|
||||
const error = errorOf(initial);
|
||||
let evidenceId,
|
||||
report,
|
||||
metadata = [],
|
||||
coverage;
|
||||
if (error) {
|
||||
assert.equal(error.code, "resource_constraint");
|
||||
const limits = error.details.reported_limits;
|
||||
evidenceId = limits.evidence_id;
|
||||
assert(evidenceId);
|
||||
const continuation = limits.continuation;
|
||||
assert(continuation, "new source-location continuation required");
|
||||
const first = contentOf(
|
||||
await rpc("tools/call", {
|
||||
name: continuation.name,
|
||||
arguments: continuation.arguments,
|
||||
}),
|
||||
).normalized_result;
|
||||
report = first.source_location;
|
||||
metadata = [...first.items];
|
||||
coverage = first.coverage;
|
||||
} else {
|
||||
const evidence = contentOf(initial),
|
||||
result = evidence.normalized_result;
|
||||
if (config.detail === "summary") {
|
||||
evidenceId = result.parent_evidence_id;
|
||||
report = result.summary;
|
||||
metadata = [...report.matches];
|
||||
coverage = result.coverage;
|
||||
} else {
|
||||
assert.equal(
|
||||
createHash("sha256")
|
||||
.update(evidence.raw_result.source_map_text)
|
||||
.digest("hex"),
|
||||
source.sha256,
|
||||
"Complete initial MCP response must preserve exact raw map bytes",
|
||||
);
|
||||
evidenceId = evidence.evidence_id;
|
||||
report = result;
|
||||
metadata = [...report.matches];
|
||||
coverage = { exhausted: true };
|
||||
}
|
||||
}
|
||||
const reference = { kind: "retained-evidence", evidence_id: evidenceId };
|
||||
while (!coverage.exhausted) {
|
||||
const page = contentOf(
|
||||
await rpc("tools/call", {
|
||||
name: "inspect_analysis_view",
|
||||
arguments: {
|
||||
source: reference,
|
||||
view: {
|
||||
kind: "source-location",
|
||||
collection: "matches",
|
||||
offset: coverage.next_offset,
|
||||
limit: 64,
|
||||
},
|
||||
},
|
||||
}),
|
||||
).normalized_result;
|
||||
metadata.push(...page.items);
|
||||
coverage = page.coverage;
|
||||
}
|
||||
assert.equal(report.source_map.sha256, source.sha256);
|
||||
assert.equal(metadata.length, source.expected_matches.length);
|
||||
const answer = [];
|
||||
for (let index = 0; index < metadata.length; index++) {
|
||||
const match = metadata[index],
|
||||
expected = source.expected_matches[index];
|
||||
assert.equal(
|
||||
match.state,
|
||||
expected.generated_only ? "generated-only" : "mapped",
|
||||
);
|
||||
if (match.state === "generated-only") {
|
||||
answer.push(match);
|
||||
continue;
|
||||
}
|
||||
assert.equal(match.source_index, expected.source_index);
|
||||
assert.equal(match.reported_source, expected.source);
|
||||
assert.equal(match.original_position.line, expected.original_line);
|
||||
assert.equal(match.original_position.column, expected.original_column);
|
||||
assert.equal(match.content.utf8_sha256, expected.content_sha256);
|
||||
const full = artifact.sourcesContent[expected.source_index],
|
||||
point = match.original_position.offset;
|
||||
assert(point !== null);
|
||||
let offset = Math.max(0, point - 256);
|
||||
if (
|
||||
offset > 0 &&
|
||||
full.charCodeAt(offset) >= 0xdc00 &&
|
||||
full.charCodeAt(offset) <= 0xdfff
|
||||
)
|
||||
offset--;
|
||||
let snippet;
|
||||
if ("text" in match.content) {
|
||||
assert.equal(
|
||||
createHash("sha256").update(match.content.text).digest("hex"),
|
||||
expected.content_sha256,
|
||||
);
|
||||
let end = Math.min(full.length, offset + 512);
|
||||
if (
|
||||
end < full.length &&
|
||||
full.charCodeAt(end - 1) >= 0xd800 &&
|
||||
full.charCodeAt(end - 1) <= 0xdbff
|
||||
)
|
||||
end--;
|
||||
snippet = match.content.text.slice(offset, end);
|
||||
} else {
|
||||
const text = contentOf(
|
||||
await rpc("tools/call", {
|
||||
name: "inspect_analysis_view",
|
||||
arguments: {
|
||||
source: reference,
|
||||
view: {
|
||||
kind: "source-location",
|
||||
collection: "source-text",
|
||||
match_index: match.match_index,
|
||||
offset,
|
||||
limit: 512,
|
||||
},
|
||||
},
|
||||
}),
|
||||
).normalized_result;
|
||||
assert.equal(text.match.content.utf8_sha256, expected.content_sha256);
|
||||
snippet = text.item.text;
|
||||
}
|
||||
assert.equal(snippet, full.slice(offset, offset + snippet.length));
|
||||
assert(offset <= point && offset + snippet.length >= point);
|
||||
const { text: _text, ...identity } = match.content;
|
||||
answer.push({
|
||||
...match,
|
||||
match_index: index,
|
||||
content: {
|
||||
...identity,
|
||||
code_units: full.length,
|
||||
snippet: {
|
||||
offset_utf16: offset,
|
||||
length_utf16: snippet.length,
|
||||
text: snippet,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
let exported = false;
|
||||
if (config.exportPath) {
|
||||
const result = await rpc("tools/call", {
|
||||
name: "export_evidence_bundle",
|
||||
arguments: { path: config.exportPath },
|
||||
});
|
||||
assert(!result.isError);
|
||||
const bundle = JSON.parse(await readFile(config.exportPath, "utf8"));
|
||||
const parent = bundle.records.find((r) => r.evidence_id === evidenceId);
|
||||
assert(parent);
|
||||
assert.equal(
|
||||
createHash("sha256")
|
||||
.update(parent.raw_result.source_map_text)
|
||||
.digest("hex"),
|
||||
source.sha256,
|
||||
);
|
||||
exported = true;
|
||||
}
|
||||
const result = {
|
||||
config,
|
||||
evidence_id: evidenceId,
|
||||
source_map_sha256: source.sha256,
|
||||
lookup: report.lookup,
|
||||
matches: answer,
|
||||
export_verified: exported,
|
||||
calls: calls.map((c) => ({
|
||||
method: c.request.method,
|
||||
tool: c.request.params?.name,
|
||||
wire_bytes: c.wire_bytes,
|
||||
elapsed_ms: c.elapsed_ms,
|
||||
isError: c.reply?.result?.isError ?? false,
|
||||
})),
|
||||
};
|
||||
if (config.resultPath)
|
||||
await writeFile(
|
||||
config.resultPath,
|
||||
JSON.stringify({ config, definition, calls, answer: result }),
|
||||
{ flag: "wx" },
|
||||
);
|
||||
console.log(JSON.stringify(result));
|
||||
} finally {
|
||||
child.stdin.end();
|
||||
if (child.exitCode === null && child.signalCode === null)
|
||||
await new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
child.kill("SIGTERM");
|
||||
reject(Error("MCP close timeout"));
|
||||
}, 5000);
|
||||
child.once("exit", () => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -140,10 +140,10 @@ check(
|
||||
// inputBytes values with a different policy (an overridable pwntools admission,
|
||||
// historical capture intake) are not this budget.
|
||||
check(
|
||||
"inline resident-input budget (use BOUNDED_PRODUCER_LIMITS from src/process/LinuxResourceLimits)",
|
||||
"inline resident-input budget (use BOUNDED_PRODUCER_LIMITS from src/domain/producerResourceLimits)",
|
||||
files,
|
||||
/input_bytes:\s*32 \* 1024 \* 1024/,
|
||||
["src/process/LinuxResourceLimits.ts"],
|
||||
["src/domain/producerResourceLimits.ts"],
|
||||
);
|
||||
|
||||
// Fixed-arity backtick keys audited 2026-10-10: every prefix field is NUL-free
|
||||
|
||||
@@ -1,52 +1,3 @@
|
||||
export const inspectionLimits = {
|
||||
referenceOffset: 0,
|
||||
referenceLimit: 100,
|
||||
resourceOffset: 0,
|
||||
resourceLimit: 100,
|
||||
attributeOffset: 0,
|
||||
attributeLimit: 100,
|
||||
maxMetadataBytes: 1024 * 1024,
|
||||
maxTableRows: 1_000,
|
||||
maxHeapItemBytes: 1024 * 1024,
|
||||
};
|
||||
|
||||
export const memberLimits = {
|
||||
typeOffset: 0,
|
||||
typeLimit: 100,
|
||||
methodOffset: 0,
|
||||
methodLimit: 100,
|
||||
fieldOffset: 0,
|
||||
fieldLimit: 100,
|
||||
memberRefOffset: 0,
|
||||
memberRefLimit: 100,
|
||||
edgeOffset: 0,
|
||||
edgeLimit: 100,
|
||||
instructionAnchorLimit: 100,
|
||||
maxMetadataBytes: 1024 * 1024,
|
||||
maxTableRows: 1_000,
|
||||
maxHeapItemBytes: 1024 * 1024,
|
||||
maxMethodBodyBytes: 1024 * 1024,
|
||||
maxMethodInstructions: 1_000,
|
||||
};
|
||||
|
||||
export const nativeBoundaryLimits = {
|
||||
moduleRefOffset: 0,
|
||||
moduleRefLimit: 100,
|
||||
importOffset: 0,
|
||||
importLimit: 100,
|
||||
implementationOffset: 0,
|
||||
implementationLimit: 100,
|
||||
maxMetadataBytes: 1024 * 1024,
|
||||
maxTableRows: 1_000,
|
||||
maxHeapItemBytes: 1024 * 1024,
|
||||
};
|
||||
|
||||
export const comparisonLimits = {
|
||||
max_method_matches: 100,
|
||||
max_field_matches: 100,
|
||||
max_candidates: 20,
|
||||
};
|
||||
|
||||
export const defaultIlBody = Buffer.from([
|
||||
0x32, 0x02, 0x7b, 0x01, 0x00, 0x00, 0x04, 0x28, 0x01, 0x00, 0x00, 0x0a, 0x2a,
|
||||
]);
|
||||
@@ -19,13 +19,7 @@ import {
|
||||
buildNativePeFixture,
|
||||
} from "../../fixtures/managed/pe.mjs";
|
||||
import { createManagedConformanceSupport } from "./support.mjs";
|
||||
import {
|
||||
comparisonLimits,
|
||||
defaultIlBody,
|
||||
inspectionLimits,
|
||||
memberLimits,
|
||||
nativeBoundaryLimits,
|
||||
} from "./config.mjs";
|
||||
import { defaultIlBody } from "./config.mjs";
|
||||
import { createManagedCompletionReport } from "./completion-report.mjs";
|
||||
import {
|
||||
completeVerifierRun,
|
||||
@@ -45,8 +39,6 @@ const {
|
||||
sha256,
|
||||
} = createManagedConformanceSupport({
|
||||
workspace,
|
||||
inspectionLimits,
|
||||
memberLimits,
|
||||
defaultIlBody,
|
||||
});
|
||||
|
||||
@@ -58,7 +50,6 @@ try {
|
||||
const modernArtifact = inspectManagedArtifactBytes(
|
||||
modern.bytes,
|
||||
modern.target,
|
||||
inspectionLimits,
|
||||
);
|
||||
assert.equal(modernArtifact.classification.status, "managed");
|
||||
assert.equal(modernArtifact.classification.runtime_family, "modern-dotnet");
|
||||
@@ -83,7 +74,6 @@ try {
|
||||
const frameworkArtifact = inspectManagedArtifactBytes(
|
||||
framework.bytes,
|
||||
framework.target,
|
||||
inspectionLimits,
|
||||
);
|
||||
assert.equal(
|
||||
frameworkArtifact.classification.runtime_family,
|
||||
@@ -93,7 +83,6 @@ try {
|
||||
const frameworkBoundaries = inspectManagedNativeBoundariesBytes(
|
||||
framework.bytes,
|
||||
framework.target,
|
||||
nativeBoundaryLimits,
|
||||
);
|
||||
assert.equal(frameworkBoundaries.pinvoke_imports.length, 1);
|
||||
assert.equal(
|
||||
@@ -108,14 +97,13 @@ try {
|
||||
const frameworkMembers = inspectManagedMembersBytes(
|
||||
framework.bytes,
|
||||
framework.target,
|
||||
memberLimits,
|
||||
);
|
||||
const frameworkArtifactEvidence = createEvidence(
|
||||
framework.target,
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
{
|
||||
operation: "inspect_managed_artifact",
|
||||
parameters: inspectionLimits,
|
||||
parameters: {},
|
||||
result: frameworkArtifact,
|
||||
rawResult: null,
|
||||
limitations: frameworkArtifact.limitations,
|
||||
@@ -127,7 +115,7 @@ try {
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: memberLimits,
|
||||
parameters: {},
|
||||
result: frameworkMembers,
|
||||
rawResult: null,
|
||||
limitations: frameworkMembers.limitations,
|
||||
@@ -139,7 +127,7 @@ try {
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
{
|
||||
operation: "inspect_managed_native_boundaries",
|
||||
parameters: nativeBoundaryLimits,
|
||||
parameters: {},
|
||||
result: frameworkBoundaries,
|
||||
rawResult: null,
|
||||
limitations: frameworkBoundaries.limitations,
|
||||
@@ -221,7 +209,6 @@ try {
|
||||
const r2rArtifact = inspectManagedArtifactBytes(
|
||||
readyToRun.bytes,
|
||||
readyToRun.target,
|
||||
inspectionLimits,
|
||||
);
|
||||
assert.equal(r2rArtifact.pe.architecture, "x86_64");
|
||||
assert.equal(
|
||||
@@ -231,7 +218,6 @@ try {
|
||||
const r2rBoundaries = inspectManagedNativeBoundariesBytes(
|
||||
readyToRun.bytes,
|
||||
readyToRun.target,
|
||||
nativeBoundaryLimits,
|
||||
);
|
||||
assert.equal(r2rBoundaries.summary.ready_to_run, true);
|
||||
assert.equal(r2rBoundaries.native_implementations[0]?.code_type, "native");
|
||||
@@ -248,7 +234,6 @@ try {
|
||||
const obfuscatedMembers = inspectManagedMembersBytes(
|
||||
obfuscated.bytes,
|
||||
obfuscated.target,
|
||||
memberLimits,
|
||||
);
|
||||
assert.equal(obfuscatedMembers.types[0]?.full_name, "Fixture.ꙮType");
|
||||
assert.equal(obfuscatedMembers.methods[0]?.name, "λ⛧");
|
||||
@@ -309,11 +294,6 @@ try {
|
||||
const comparison = await compareManagedMemberPaths({
|
||||
leftPath: left.path,
|
||||
rightPath: right.path,
|
||||
memberLimits: {
|
||||
maxFileBytes: 1024 * 1024,
|
||||
...memberLimits,
|
||||
},
|
||||
comparisonLimits,
|
||||
});
|
||||
assert.equal(comparison.ok, true);
|
||||
const comparisonResult = comparison.value.normalized_result;
|
||||
@@ -341,11 +321,6 @@ try {
|
||||
const partialBodyComparison = await compareManagedMemberPaths({
|
||||
leftPath: unavailableBodyLeft.path,
|
||||
rightPath: unavailableBodyRight.path,
|
||||
memberLimits: {
|
||||
maxFileBytes: 1024 * 1024,
|
||||
...memberLimits,
|
||||
},
|
||||
comparisonLimits,
|
||||
});
|
||||
assert.equal(partialBodyComparison.ok, true);
|
||||
const partialBodyMethod =
|
||||
@@ -363,7 +338,6 @@ try {
|
||||
const nativeResult = inspectManagedArtifactBytes(
|
||||
nativeOnly.bytes,
|
||||
nativeOnly.target,
|
||||
inspectionLimits,
|
||||
);
|
||||
assert.equal(nativeResult.classification.status, "not-managed");
|
||||
|
||||
@@ -373,7 +347,6 @@ try {
|
||||
const malformedResult = inspectManagedArtifactBytes(
|
||||
malformed.bytes,
|
||||
malformed.target,
|
||||
inspectionLimits,
|
||||
);
|
||||
assert.equal(malformedResult.classification.status, "malformed");
|
||||
|
||||
|
||||
@@ -14,13 +14,7 @@ import {
|
||||
parseManagedAppManifest,
|
||||
} from "./manifest-parser.mjs";
|
||||
|
||||
export const createManagedManifestVerifier = (context) => ({
|
||||
verifyManagedAppManifest: (rawManifest, manifestPath) =>
|
||||
verifyManagedAppManifest(context, rawManifest, manifestPath),
|
||||
});
|
||||
|
||||
async function verifyManagedAppManifest(context, rawManifest, manifestPath) {
|
||||
const { appManifestInspectionLimits } = context;
|
||||
export async function verifyManagedAppManifest(rawManifest, manifestPath) {
|
||||
const manifest = parseManagedAppManifest(rawManifest);
|
||||
const targetPath = isAbsolute(manifest.target.path)
|
||||
? manifest.target.path
|
||||
@@ -36,25 +30,15 @@ async function verifyManagedAppManifest(context, rawManifest, manifestPath) {
|
||||
`target sha256 mismatch: expected ${manifest.target.sha256}, observed ${target.sha256}`,
|
||||
);
|
||||
const bytes = await readFile(targetPath);
|
||||
const artifact = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
target,
|
||||
appManifestInspectionLimits,
|
||||
);
|
||||
const artifact = inspectManagedArtifactBytes(bytes, target);
|
||||
verifyManifestTarget(manifest, artifact);
|
||||
const methodVerification = verifyManifestMethods(
|
||||
context,
|
||||
manifest,
|
||||
bytes,
|
||||
target,
|
||||
);
|
||||
const methodVerification = verifyManifestMethods(manifest, bytes, target);
|
||||
const { methods, inspectedMethods } = methodVerification;
|
||||
let assertions = 1 + methodVerification.assertions;
|
||||
const applicationGraph =
|
||||
manifest.application_graph === undefined
|
||||
? undefined
|
||||
: verifyManagedAppManifestApplicationGraph({
|
||||
context,
|
||||
graphManifest: manifest.application_graph,
|
||||
bytes,
|
||||
target,
|
||||
@@ -109,17 +93,12 @@ function verifyManifestTarget(manifest, artifact) {
|
||||
);
|
||||
}
|
||||
|
||||
function verifyManifestMethods(context, manifest, bytes, target) {
|
||||
function verifyManifestMethods(manifest, bytes, target) {
|
||||
const methods = [];
|
||||
let assertions = 0;
|
||||
const inspectedMethods = new Map();
|
||||
for (const expectedMethod of manifest.methods) {
|
||||
const method = inspectManagedMethodByToken(
|
||||
context,
|
||||
bytes,
|
||||
target,
|
||||
expectedMethod,
|
||||
);
|
||||
const method = inspectManagedMethodByToken(bytes, target, expectedMethod);
|
||||
ensure(
|
||||
method.signature.raw_sha256 === expectedMethod.signature_sha256,
|
||||
`method ${expectedMethod.token} signature sha256 mismatch: expected ${expectedMethod.signature_sha256}, observed ${method.signature.raw_sha256}`,
|
||||
@@ -154,7 +133,6 @@ function verifyManifestMethods(context, manifest, bytes, target) {
|
||||
}
|
||||
|
||||
function verifyManagedAppManifestApplicationGraph({
|
||||
context,
|
||||
graphManifest,
|
||||
bytes,
|
||||
target,
|
||||
@@ -164,7 +142,6 @@ function verifyManagedAppManifestApplicationGraph({
|
||||
}) {
|
||||
const graphByToken = new Map();
|
||||
const projectionState = {
|
||||
context,
|
||||
graphByToken,
|
||||
inspectedMethods,
|
||||
bytes,
|
||||
@@ -238,8 +215,6 @@ function verifyManagedAppManifestApplicationGraph({
|
||||
}
|
||||
|
||||
const projectManifestMethod = (state, token) => {
|
||||
const { appManifestInspectionLimits, appManifestMemberLimits } =
|
||||
state.context;
|
||||
const existing = state.graphByToken.get(token);
|
||||
if (existing !== undefined) return existing;
|
||||
const method = state.inspectedMethods.get(token);
|
||||
@@ -248,7 +223,6 @@ const projectManifestMethod = (state, token) => {
|
||||
`application graph method ${token} must also be declared in manifest.methods`,
|
||||
);
|
||||
const members = inspectManagedMethodPageByToken(
|
||||
state.context,
|
||||
state.bytes,
|
||||
state.target,
|
||||
token,
|
||||
@@ -258,7 +232,7 @@ const projectManifestMethod = (state, token) => {
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
{
|
||||
operation: "inspect_managed_artifact",
|
||||
parameters: appManifestInspectionLimits,
|
||||
parameters: {},
|
||||
result: state.artifact,
|
||||
rawResult: null,
|
||||
limitations: state.artifact.limitations,
|
||||
@@ -268,7 +242,7 @@ const projectManifestMethod = (state, token) => {
|
||||
const memberEvidence = createEvidence(state.target, MANAGED_STATIC_PROVIDER, {
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {
|
||||
...appManifestMemberLimits,
|
||||
methodLimit: 1,
|
||||
methodOffset: methodTokenRow(token) - 1,
|
||||
},
|
||||
result: members,
|
||||
@@ -298,9 +272,8 @@ const projectManifestMethod = (state, token) => {
|
||||
return projection;
|
||||
};
|
||||
|
||||
function inspectManagedMethodByToken(context, bytes, target, expectedMethod) {
|
||||
function inspectManagedMethodByToken(bytes, target, expectedMethod) {
|
||||
const members = inspectManagedMethodPageByToken(
|
||||
context,
|
||||
bytes,
|
||||
target,
|
||||
expectedMethod.token,
|
||||
@@ -319,11 +292,10 @@ function inspectManagedMethodByToken(context, bytes, target, expectedMethod) {
|
||||
return method;
|
||||
}
|
||||
|
||||
function inspectManagedMethodPageByToken(context, bytes, target, token) {
|
||||
const { appManifestMemberLimits } = context;
|
||||
function inspectManagedMethodPageByToken(bytes, target, token) {
|
||||
const row = methodTokenRow(token);
|
||||
return inspectManagedMembersBytes(bytes, target, {
|
||||
...appManifestMemberLimits,
|
||||
methodLimit: 1,
|
||||
methodOffset: row - 1,
|
||||
});
|
||||
}
|
||||
@@ -10,13 +10,12 @@ import { inspectManagedMembersBytes } from "../../../dist/dotnet/ManagedMemberIn
|
||||
import { importManagedReconstructionEvidence } from "../../../dist/application/managed/ManagedReconstructionService.js";
|
||||
import { createEvidence } from "../../../dist/domain/evidence.js";
|
||||
import { MANAGED_STATIC_PROVIDER } from "../../../dist/domain/investigationProviders.js";
|
||||
import { createManagedManifestVerifier } from "./manifest.mjs";
|
||||
import { verifyManagedAppManifest } from "./manifest.mjs";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
export const createManagedConformanceOracleSupport = (context) => {
|
||||
const verifier = createManagedManifestVerifier(context);
|
||||
const support = { ...context, ...verifier };
|
||||
const support = { ...context, verifyManagedAppManifest };
|
||||
return {
|
||||
runManagedAppManifestSelfTest: () => runManagedAppManifestSelfTest(support),
|
||||
runOptionalManagedAppManifest: () => runOptionalManagedAppManifest(support),
|
||||
@@ -29,16 +28,8 @@ async function runManagedAppManifestSelfTest(context) {
|
||||
methodName: "PinnedSemanticSlice",
|
||||
ilBody: context.defaultIlBody,
|
||||
});
|
||||
const artifact = inspectManagedArtifactBytes(
|
||||
sample.bytes,
|
||||
sample.target,
|
||||
context.inspectionLimits,
|
||||
);
|
||||
const members = inspectManagedMembersBytes(
|
||||
sample.bytes,
|
||||
sample.target,
|
||||
context.memberLimits,
|
||||
);
|
||||
const artifact = inspectManagedArtifactBytes(sample.bytes, sample.target);
|
||||
const members = inspectManagedMembersBytes(sample.bytes, sample.target);
|
||||
const method = members.methods[0];
|
||||
assert.ok(method);
|
||||
const summary = await context.verifyManagedAppManifest(
|
||||
@@ -132,16 +123,8 @@ async function runOptionalIlspyOracle(context) {
|
||||
const sample = await context.fixture("ilspy-oracle.exe", {
|
||||
methodName: "PinnedSemanticSlice",
|
||||
});
|
||||
const artifact = inspectManagedArtifactBytes(
|
||||
sample.bytes,
|
||||
sample.target,
|
||||
context.inspectionLimits,
|
||||
);
|
||||
const members = inspectManagedMembersBytes(
|
||||
sample.bytes,
|
||||
sample.target,
|
||||
context.memberLimits,
|
||||
);
|
||||
const artifact = inspectManagedArtifactBytes(sample.bytes, sample.target);
|
||||
const members = inspectManagedMembersBytes(sample.bytes, sample.target);
|
||||
const method = members.methods[0];
|
||||
ensureIlspy(method !== undefined, "source-owned fixture has no method");
|
||||
const listOutput = await runIlspy(
|
||||
@@ -167,7 +150,6 @@ async function runOptionalIlspyOracle(context) {
|
||||
"ilspycmd C# output exceeded the managed reconstruction import bound",
|
||||
);
|
||||
const reconstructionImport = importIlspyReconstruction({
|
||||
context,
|
||||
sample,
|
||||
method,
|
||||
members,
|
||||
@@ -210,7 +192,6 @@ async function runOptionalIlspyOracle(context) {
|
||||
}
|
||||
|
||||
function importIlspyReconstruction({
|
||||
context,
|
||||
sample,
|
||||
method,
|
||||
members,
|
||||
@@ -223,7 +204,7 @@ function importIlspyReconstruction({
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: context.memberLimits,
|
||||
parameters: {},
|
||||
result: members,
|
||||
rawResult: null,
|
||||
limitations: members.limitations,
|
||||
|
||||
@@ -17,32 +17,11 @@ export const createManagedConformanceSupport = (context) => {
|
||||
const fixture = (name, options) =>
|
||||
fixtureBytes(name, buildManagedPeFixture(options));
|
||||
const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
||||
const appManifestInspectionLimits = {
|
||||
...context.inspectionLimits,
|
||||
maxMetadataBytes: 64 * 1024 * 1024,
|
||||
maxTableRows: 250_000,
|
||||
maxHeapItemBytes: 16 * 1024 * 1024,
|
||||
};
|
||||
const appManifestMemberLimits = {
|
||||
...context.memberLimits,
|
||||
typeLimit: 1,
|
||||
methodLimit: 1,
|
||||
fieldLimit: 1,
|
||||
memberRefLimit: 1,
|
||||
edgeLimit: 1,
|
||||
maxMetadataBytes: 64 * 1024 * 1024,
|
||||
maxTableRows: 250_000,
|
||||
maxHeapItemBytes: 16 * 1024 * 1024,
|
||||
maxMethodBodyBytes: 4 * 1024 * 1024,
|
||||
maxMethodInstructions: 20_000,
|
||||
};
|
||||
const base = {
|
||||
...context,
|
||||
fixture,
|
||||
fixtureBytes,
|
||||
sha256,
|
||||
appManifestInspectionLimits,
|
||||
appManifestMemberLimits,
|
||||
};
|
||||
return { ...base, ...createManagedConformanceOracleSupport(base) };
|
||||
};
|
||||
Reference in new issue
Block a user