deepbom 1.103.1 → 1.105.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,10 +7,10 @@ For a local Codex or Claude Code project, preview and install the bundled Agent
7
7
  Skill without operating an analysis server:
8
8
 
9
9
  ```console
10
- npx -y deepbom@1.103.1 integrate codex
11
- npx -y deepbom@1.103.1 integrate codex --apply
12
- npx -y deepbom@1.103.1 integrate claude-code
13
- npx -y deepbom@1.103.1 integrate claude-code --apply
10
+ npx -y deepbom@1.105.0 integrate codex
11
+ npx -y deepbom@1.105.0 integrate codex --apply
12
+ npx -y deepbom@1.105.0 integrate claude-code
13
+ npx -y deepbom@1.105.0 integrate claude-code --apply
14
14
  ```
15
15
 
16
16
  ```console
@@ -28,10 +28,10 @@ npx deepbom explore model.tflite --target-profile target-profile.json
28
28
  npx deepbom audit model.pte --executorch-build deepbom.executorch-build.json --compact
29
29
  npx deepbom capabilities --format agent-json
30
30
  npx deepbom capabilities --format agent-text
31
- npx -y deepbom@1.103.1 mcp
31
+ npx -y deepbom@1.105.0 mcp
32
32
  ```
33
33
 
34
- Claude Desktop can install the version-matched `deepbom-1.103.1.mcpb` asset
34
+ Claude Desktop can install the version-matched `deepbom-1.105.0.mcpb` asset
35
35
  from the corresponding GitHub Release as a local desktop extension.
36
36
 
37
37
  ChatGPT developer-mode users can connect `https://deepbom.org/mcp` for one
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env python3
2
+ """Explicit local ONNX/LiteRT execution capture. Never imported by static audit."""
3
+ import argparse
4
+ import datetime
5
+ import hashlib
6
+ import io
7
+ import json
8
+ import math
9
+ from pathlib import Path
10
+ import sys
11
+ import uuid
12
+
13
+
14
+ def sha(data):
15
+ return hashlib.sha256(data).hexdigest()
16
+
17
+
18
+ def main():
19
+ p = argparse.ArgumentParser(description=__doc__)
20
+ p.add_argument("model", type=Path)
21
+ p.add_argument("--model-ir", type=Path, required=True)
22
+ p.add_argument("--output", type=Path, required=True)
23
+ p.add_argument("--allow-execution", action="store_true")
24
+ group = p.add_mutually_exclusive_group(required=True)
25
+ group.add_argument("--inputs-npz", type=Path)
26
+ group.add_argument("--probe", choices=["ones", "zeros", "identity"])
27
+ p.add_argument("--max-values", type=int, default=1_000_000)
28
+ p.add_argument("--outputs-only", action="store_true")
29
+ p.add_argument("--entry-region", help="Exact Model IR entry region ID for a model with multiple entries")
30
+ args = p.parse_args()
31
+ if not args.allow_execution:
32
+ p.error("Execution requires --allow-execution. Static audit never invokes this collector.")
33
+ if args.output.exists():
34
+ p.error("Output already exists; choose a new capture path.")
35
+ if args.max_values < 1 or args.max_values > 1_000_000:
36
+ p.error("--max-values must be between 1 and 1000000.")
37
+ import numpy as np
38
+ model_ir = json.loads(args.model_ir.read_text())
39
+ if model_ir.get("schema") == "deepbom.analysis_selection.v1":
40
+ model_ir = model_ir["sections"]["model_ir"]
41
+ raw = args.model.read_bytes()
42
+ if sha(raw) != model_ir["artifact"]["sha256"]:
43
+ raise ValueError("Model bytes do not match Model IR.")
44
+ source = {"model_ir_sha256": model_ir["model_ir_sha256"], "artifact_sha256": sha(raw), "artifact_set_sha256": model_ir["artifact_set"]["artifact_set_sha256"]}
45
+ values = model_ir["program"]["values"]
46
+ entries = [entry for program in model_ir["program"]["programs"] for entry in program["entry_region_refs"]]
47
+ if len(entries) != 1 and not args.entry_region:
48
+ raise ValueError("Multiple entry regions require --entry-region.")
49
+ entry = args.entry_region or entries[0]
50
+ if entry not in entries:
51
+ raise ValueError("Unknown entry region.")
52
+ expected_entry = {"onnx": "region:scope:onnx:main_graph", "tflite": "region:scope:tflite:subgraph:0"}.get(model_ir["artifact"]["format"])
53
+ if entry != expected_entry:
54
+ raise ValueError("This collector supports the primary ONNX graph or TFLite subgraph only.")
55
+ inputs = [v for v in values if "graph_input" in v["roles"] and v["region_ref"] == entry and not v["storage_refs"]]
56
+ eligible = [v for v in values if not ("graph_input" in v["roles"] and v["region_ref"] == entry) and not v["storage_refs"]]
57
+ requested = [v for v in eligible if not args.outputs_only or "graph_output" in v["roles"]]
58
+ dtype_map = {"FLOAT32": np.float32, "FLOAT16": np.float16, "FLOAT64": np.float64, "INT64": np.int64, "INT32": np.int32, "INT16": np.int16, "INT8": np.int8, "UINT64": np.uint64, "UINT32": np.uint32, "UINT16": np.uint16, "UINT8": np.uint8, "BOOL": np.bool_}
59
+ input_bytes = args.inputs_npz.read_bytes() if args.inputs_npz else None
60
+ npz = np.load(io.BytesIO(input_bytes), allow_pickle=False) if input_bytes is not None else None
61
+ feeds = {}
62
+ for item in inputs:
63
+ if item["name"] in feeds:
64
+ raise ValueError("Input names are ambiguous across serialized regions.")
65
+ dtype = dtype_map[item["dtype"]]
66
+ if npz is not None:
67
+ array = npz[item["name"]]
68
+ if array.dtype != dtype:
69
+ raise ValueError(f"Input {item['name']} dtype mismatch; implicit casts are disabled.")
70
+ else:
71
+ shape = item["shape"]
72
+ if any(not isinstance(d, int) or d < 0 for d in shape):
73
+ raise ValueError("Dynamic inputs require --inputs-npz with explicit dimensions.")
74
+ if math.prod(shape) > args.max_values:
75
+ raise ValueError("Synthetic input exceeds value budget.")
76
+ if args.probe == "identity":
77
+ if len(shape) != 2 or shape[0] != shape[1]:
78
+ raise ValueError("Identity probe requires a square rank-2 input.")
79
+ array = np.eye(shape[0], dtype=dtype)
80
+ else:
81
+ array = np.full(shape, int(args.probe == "ones"), dtype=dtype)
82
+ feeds[item["name"]] = array
83
+ if npz is not None:
84
+ if set(npz.files) != set(feeds):
85
+ raise ValueError("NPZ keys must exactly match serialized graph inputs.")
86
+ npz.close()
87
+ consumed = sum(array.size for array in feeds.values())
88
+ if consumed > args.max_values:
89
+ raise ValueError("Inputs exceed value budget.")
90
+ missing, selected = [], []
91
+ for item in requested:
92
+ shape = item["shape"]
93
+ expected = math.prod(shape) if all(isinstance(d, int) and d >= 0 for d in shape) else None
94
+ if item["dtype"] not in dtype_map:
95
+ missing.append({"value_ref": item["id"], "reason": "collector_dtype_unsupported"})
96
+ elif expected is None or consumed + expected > args.max_values:
97
+ missing.append({"value_ref": item["id"], "reason": "shape_or_capture_budget_unavailable"})
98
+ else:
99
+ selected.append(item)
100
+ consumed += expected
101
+ captures = []
102
+ def encode(item, array):
103
+ if str(array.dtype) not in [str(np.dtype(dtype_map[item["dtype"]]))]:
104
+ raise ValueError("Runtime capture dtype differs from Model IR.")
105
+ if array.size > args.max_values:
106
+ raise ValueError("Runtime tensor exceeds capture budget.")
107
+ numeric = []
108
+ for v in array.reshape(-1):
109
+ if array.dtype.kind in "iu": numeric.append(str(int(v)) if array.dtype.itemsize == 8 else int(v))
110
+ elif array.dtype.kind == "b": numeric.append(int(v))
111
+ else:
112
+ n = float(v)
113
+ numeric.append("NaN" if math.isnan(n) else "+Infinity" if n == math.inf else "-Infinity" if n == -math.inf else "-0" if n == 0 and math.copysign(1, n) < 0 else n)
114
+ return {"value_ref": item["id"], "native_locator": item["name"], "dtype": item["dtype"], "shape": list(array.shape), "values": numeric}
115
+ started = datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
116
+ instrumented_hash = None
117
+ configuration = {"collector_version": "1.0.0", "max_values": args.max_values, "outputs_only": args.outputs_only, "probe": args.probe, "threads": 1, "numpy_version": np.__version__, "python_version": sys.version.split()[0], "inputs_npz_sha256": sha(input_bytes) if input_bytes is not None else None}
118
+ if model_ir["artifact"]["format"] == "onnx":
119
+ import onnx
120
+ import onnxruntime as ort
121
+ configuration["onnx_version"] = onnx.__version__
122
+ parsed = onnx.load_model_from_string(raw)
123
+ if any(t.data_location == onnx.TensorProto.EXTERNAL for t in parsed.graph.initializer):
124
+ raise ValueError("This collector requires inline ONNX data; external artifacts need an independently bound collector.")
125
+ try: parsed = onnx.shape_inference.infer_shapes(parsed)
126
+ except (ValueError, RuntimeError): pass
127
+ known = {v.name: v for v in list(parsed.graph.value_info) + list(parsed.graph.input) + list(parsed.graph.output)}
128
+ outputs = {v.name for v in parsed.graph.output}
129
+ accepted = []
130
+ for item in selected:
131
+ if item["region_ref"] != "region:scope:onnx:main_graph":
132
+ missing.append({"value_ref": item["id"], "reason": "nested_region_capture_not_supported"})
133
+ continue
134
+ if item["name"] not in outputs:
135
+ if item["name"] not in known:
136
+ missing.append({"value_ref": item["id"], "reason": "onnx_value_type_not_inferred"})
137
+ continue
138
+ parsed.graph.output.append(known[item["name"]])
139
+ outputs.add(item["name"])
140
+ accepted.append(item)
141
+ instrumented = parsed.SerializeToString()
142
+ instrumented_hash = sha(instrumented)
143
+ options = ort.SessionOptions()
144
+ options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_DISABLE_ALL
145
+ options.intra_op_num_threads = options.inter_op_num_threads = 1
146
+ session = ort.InferenceSession(instrumented, options, providers=["CPUExecutionProvider"])
147
+ if not accepted:
148
+ raise ValueError("No requested activations are collectible within the bound; no run was made.")
149
+ arrays = session.run([v["name"] for v in accepted], feeds)
150
+ captures = [encode(item, array) for item, array in zip(accepted, arrays)]
151
+ runtime = {"name": "onnxruntime", "version": ort.__version__, "configured_providers": session.get_providers(), "device": "CPU"}
152
+ configuration["graph_optimizations"] = "disabled"
153
+ elif model_ir["artifact"]["format"] == "tflite":
154
+ from ai_edge_litert.interpreter import Interpreter, OpResolverType
155
+ from importlib.metadata import version
156
+ interpreter = Interpreter(model_content=raw, num_threads=1, experimental_preserve_all_tensors=True, experimental_op_resolver_type=OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES)
157
+ for item in interpreter.get_input_details():
158
+ interpreter.resize_tensor_input(item["index"], feeds[item["name"]].shape, strict=True)
159
+ interpreter.allocate_tensors()
160
+ for item in interpreter.get_input_details(): interpreter.set_tensor(item["index"], feeds[item["name"]])
161
+ interpreter.invoke()
162
+ for item in selected:
163
+ if item["region_ref"] != "region:scope:tflite:subgraph:0":
164
+ missing.append({"value_ref": item["id"], "reason": "non_primary_subgraph_not_collected"})
165
+ continue
166
+ try: array = interpreter.get_tensor(item["native_index"])
167
+ except ValueError:
168
+ missing.append({"value_ref": item["id"], "reason": "runtime_did_not_preserve_tensor"})
169
+ continue
170
+ captures.append(encode(item, array))
171
+ runtime = {"name": "ai-edge-litert", "version": version("ai-edge-litert"), "configured_providers": ["builtin_without_default_delegates"], "device": "CPU"}
172
+ configuration["preserve_all_tensors"] = True
173
+ else:
174
+ raise ValueError("Built-in capture supports ONNX and TFLite. Other runtimes can produce the same capture contract; runtime support is not inferred from file format.")
175
+ if sum(len(t["values"]) for t in captures) + sum(a.size for a in feeds.values()) > args.max_values:
176
+ raise ValueError("Actual run exceeds capture value budget; no partial success is emitted.")
177
+ if args.model.read_bytes() != raw:
178
+ raise ValueError("Artifact changed during capture.")
179
+ run = {"id": str(uuid.uuid4()), "started_at": started, "entry_region_ref": entry, "runtime": runtime,
180
+ "collector": {"name": "deepbom-local-activation-collector", "version": "1.0.0", "sha256": sha(Path(__file__).read_bytes())},
181
+ "execution": {"artifact_sha256": source["artifact_sha256"], "instrumented_artifact_sha256": instrumented_hash, "configuration_sha256": sha(json.dumps(configuration, sort_keys=True, separators=(",", ":")).encode()), "configuration": configuration},
182
+ "probe": {"kind": "custom" if args.inputs_npz else "synthetic_" + args.probe, "description": "User supplied numeric NPZ; preprocessing and representativeness are not independently established." if args.inputs_npz else "Synthetic " + args.probe + " input in serialized dtype; not a representative-data evaluation."}, "runtime_evidence": None}
183
+ capture = {"schema": "deepbom.activation_capture.v1", "source": source, "run": run, "inputs": [encode(item, feeds[item["name"]]) for item in inputs], "requested_value_refs": [v["id"] for v in requested], "captures": captures, "missing": missing}
184
+ encoded = json.dumps(capture, allow_nan=False, separators=(",", ":")) + "\n"
185
+ if len(encoded.encode()) > 16 * 1024 * 1024:
186
+ raise ValueError("Capture exceeds the 16 MiB import limit.")
187
+ with args.output.open("x") as output: output.write(encoded)
188
+ print(f"Captured {len(captures)}/{len(requested)} requested values; {len(missing)} unavailable. {args.output}")
189
+
190
+
191
+ if __name__ == "__main__":
192
+ try: main()
193
+ except Exception as error:
194
+ print(f"Activation capture failed: {error}", file=sys.stderr)
195
+ sys.exit(1)