rocket-engine 0.0.2b1__py3-none-any.whl
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.
- rocket_engine/__init__.py +8 -0
- rocket_engine/__main__.py +12 -0
- rocket_engine/adapters/README.md +54 -0
- rocket_engine/adapters/__init__.py +7 -0
- rocket_engine/adapters/api.py +39 -0
- rocket_engine/adapters/base.py +46 -0
- rocket_engine/adapters/cace/__init__.py +163 -0
- rocket_engine/adapters/circulax/README.md +10 -0
- rocket_engine/adapters/circulax/__init__.py +112 -0
- rocket_engine/adapters/circulax/_driver.py +190 -0
- rocket_engine/adapters/circulax/adapter.py +538 -0
- rocket_engine/adapters/circulax/results.py +291 -0
- rocket_engine/adapters/cli/README.md +34 -0
- rocket_engine/adapters/cli/__init__.py +305 -0
- rocket_engine/adapters/connectivity/__init__.py +196 -0
- rocket_engine/adapters/connectivity/results.py +214 -0
- rocket_engine/adapters/cosim/README.md +9 -0
- rocket_engine/adapters/cosim/__init__.py +1390 -0
- rocket_engine/adapters/cosim/results.py +214 -0
- rocket_engine/adapters/devsim/__init__.py +171 -0
- rocket_engine/adapters/digital_flow/README.md +9 -0
- rocket_engine/adapters/digital_flow/__init__.py +1121 -0
- rocket_engine/adapters/digital_flow/results.py +227 -0
- rocket_engine/adapters/digital_flow/state.py +462 -0
- rocket_engine/adapters/drc/README.klayout.md +7 -0
- rocket_engine/adapters/drc/README.md +7 -0
- rocket_engine/adapters/drc/__init__.py +1 -0
- rocket_engine/adapters/drc/klayout.py +952 -0
- rocket_engine/adapters/drc/magic.py +287 -0
- rocket_engine/adapters/drc/results.py +184 -0
- rocket_engine/adapters/drc/results_kl.py +188 -0
- rocket_engine/adapters/elmer/__init__.py +163 -0
- rocket_engine/adapters/envelope.py +120 -0
- rocket_engine/adapters/equivalence/__init__.py +156 -0
- rocket_engine/adapters/errors.py +181 -0
- rocket_engine/adapters/extract_magic/README.md +15 -0
- rocket_engine/adapters/extract_magic/__init__.py +425 -0
- rocket_engine/adapters/extract_magic/results.py +209 -0
- rocket_engine/adapters/formal_sby/README.md +7 -0
- rocket_engine/adapters/formal_sby/__init__.py +773 -0
- rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
- rocket_engine/adapters/formal_sby/results.py +185 -0
- rocket_engine/adapters/gdsfactory/__init__.py +194 -0
- rocket_engine/adapters/gdsfactory/_driver.py +459 -0
- rocket_engine/adapters/gdsfactory/adapter.py +609 -0
- rocket_engine/adapters/gdsfactory/results.py +181 -0
- rocket_engine/adapters/hdl_sim/README.md +7 -0
- rocket_engine/adapters/hdl_sim/__init__.py +369 -0
- rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
- rocket_engine/adapters/hdl_sim/results.py +178 -0
- rocket_engine/adapters/hdl_sim/verilator.py +167 -0
- rocket_engine/adapters/http/README.md +15 -0
- rocket_engine/adapters/http/__init__.py +458 -0
- rocket_engine/adapters/http/events.py +63 -0
- rocket_engine/adapters/ipc/README.md +9 -0
- rocket_engine/adapters/ipc/__init__.py +111 -0
- rocket_engine/adapters/lvs_netgen/README.md +20 -0
- rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
- rocket_engine/adapters/lvs_netgen/results.py +168 -0
- rocket_engine/adapters/mcp/README.md +19 -0
- rocket_engine/adapters/mcp/__init__.py +85 -0
- rocket_engine/adapters/mcp/api-schema.json +6107 -0
- rocket_engine/adapters/mcp/pull.py +246 -0
- rocket_engine/adapters/mcp/server.py +794 -0
- rocket_engine/adapters/meep/__init__.py +167 -0
- rocket_engine/adapters/meow/README.md +7 -0
- rocket_engine/adapters/meow/__init__.py +190 -0
- rocket_engine/adapters/meow/_driver.py +227 -0
- rocket_engine/adapters/meow/_envelope.py +108 -0
- rocket_engine/adapters/meow/adapter.py +491 -0
- rocket_engine/adapters/meow/results.py +218 -0
- rocket_engine/adapters/mutation/__init__.py +192 -0
- rocket_engine/adapters/ngspice/README.md +28 -0
- rocket_engine/adapters/ngspice/__init__.py +627 -0
- rocket_engine/adapters/ngspice/results.py +215 -0
- rocket_engine/adapters/postlayout/README.md +28 -0
- rocket_engine/adapters/postlayout/__init__.py +17 -0
- rocket_engine/adapters/postlayout/adapter.py +1062 -0
- rocket_engine/adapters/postlayout/results.py +330 -0
- rocket_engine/adapters/postlayout/translate.py +71 -0
- rocket_engine/adapters/profile.py +220 -0
- rocket_engine/adapters/sax/README.md +23 -0
- rocket_engine/adapters/sax/__init__.py +516 -0
- rocket_engine/adapters/sax/_driver.py +148 -0
- rocket_engine/adapters/sax/results.py +291 -0
- rocket_engine/adapters/stdio_console.py +1151 -0
- rocket_engine/adapters/syntax/README.md +5 -0
- rocket_engine/adapters/syntax/__init__.py +366 -0
- rocket_engine/adapters/syntax/results.py +181 -0
- rocket_engine/adapters/synth_yosys/README.md +7 -0
- rocket_engine/adapters/synth_yosys/__init__.py +655 -0
- rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
- rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
- rocket_engine/adapters/synth_yosys/results.py +173 -0
- rocket_engine/adapters/timing_sta/README.md +9 -0
- rocket_engine/adapters/timing_sta/__init__.py +726 -0
- rocket_engine/adapters/view_sync/__init__.py +185 -0
- rocket_engine/adapters/view_sync/results.py +189 -0
- rocket_engine/adapters/vlsir/__init__.py +142 -0
- rocket_engine/adapters/xschem/__init__.py +440 -0
- rocket_engine/adapters/xschem/deck.py +266 -0
- rocket_engine/clients/__init__.py +5 -0
- rocket_engine/clients/runtime.py +255 -0
- rocket_engine/composition.py +2279 -0
- rocket_engine/execution/README.md +30 -0
- rocket_engine/execution/__init__.py +150 -0
- rocket_engine/execution/apig/__init__.py +1 -0
- rocket_engine/execution/apig/pool.py +463 -0
- rocket_engine/execution/apig/submit.py +124 -0
- rocket_engine/execution/block_runs_scope.py +479 -0
- rocket_engine/execution/coalesce.py +414 -0
- rocket_engine/execution/console/__init__.py +1 -0
- rocket_engine/execution/console/apply_session.py +318 -0
- rocket_engine/execution/console/lifecycle.py +265 -0
- rocket_engine/execution/dispatch.py +205 -0
- rocket_engine/execution/errors.py +31 -0
- rocket_engine/execution/ports.py +797 -0
- rocket_engine/execution/pyexec/__init__.py +1 -0
- rocket_engine/execution/pyexec/apply_context.py +547 -0
- rocket_engine/execution/pyexec/approvals.py +173 -0
- rocket_engine/execution/pyexec/runner.py +404 -0
- rocket_engine/execution/pyexec/support.py +464 -0
- rocket_engine/execution/python_exec.py +897 -0
- rocket_engine/execution/queue.py +85 -0
- rocket_engine/execution/replay_safe.py +601 -0
- rocket_engine/execution/routing/__init__.py +1 -0
- rocket_engine/execution/routing/calls.py +364 -0
- rocket_engine/execution/routing/namespace.py +785 -0
- rocket_engine/execution/routing/ports.py +102 -0
- rocket_engine/execution/routing/tools.py +346 -0
- rocket_engine/execution/runner.py +311 -0
- rocket_engine/execution/runtime.py +1149 -0
- rocket_engine/execution/service.py +279 -0
- rocket_engine/execution/worker.py +464 -0
- rocket_engine/interfaces/README.md +26 -0
- rocket_engine/interfaces/__init__.py +58 -0
- rocket_engine/interfaces/apig_assets.py +407 -0
- rocket_engine/interfaces/celllink.py +461 -0
- rocket_engine/interfaces/documents.py +92 -0
- rocket_engine/interfaces/envelopes.py +131 -0
- rocket_engine/interfaces/errors.py +16 -0
- rocket_engine/interfaces/layout.py +410 -0
- rocket_engine/interfaces/layout_schema.yaml +278 -0
- rocket_engine/interfaces/paths.py +223 -0
- rocket_engine/interfaces/pdks.py +550 -0
- rocket_engine/interfaces/ports.py +72 -0
- rocket_engine/interfaces/references.py +115 -0
- rocket_engine/interfaces/refs.py +85 -0
- rocket_engine/interfaces/runparams.py +304 -0
- rocket_engine/interfaces/setup.py +552 -0
- rocket_engine/interfaces/setup_catalogue.yaml +243 -0
- rocket_engine/interfaces/setup_probes.py +66 -0
- rocket_engine/interfaces/vocabulary.py +58 -0
- rocket_engine/interfaces/yaml_dump.py +31 -0
- rocket_engine/library/README.md +22 -0
- rocket_engine/library/__init__.py +13 -0
- rocket_engine/library/apig_catalog.py +371 -0
- rocket_engine/library/catalog.py +185 -0
- rocket_engine/library/errors.py +16 -0
- rocket_engine/mutate/__init__.py +776 -0
- rocket_engine/mutate/operators.py +700 -0
- rocket_engine/ops/__init__.py +1 -0
- rocket_engine/ops/deferred.py +279 -0
- rocket_engine/ops/editors.py +404 -0
- rocket_engine/ops/evidence.py +194 -0
- rocket_engine/ops/gates.py +262 -0
- rocket_engine/ops/ports.py +76 -0
- rocket_engine/ops/queries.py +417 -0
- rocket_engine/ops/runs.py +553 -0
- rocket_engine/ops/submit.py +614 -0
- rocket_engine/project/README.md +27 -0
- rocket_engine/project/__init__.py +6 -0
- rocket_engine/project/canvas/__init__.py +55 -0
- rocket_engine/project/canvas/compile.py +109 -0
- rocket_engine/project/canvas/integrate.py +48 -0
- rocket_engine/project/canvas/schema.py +241 -0
- rocket_engine/project/canvas/templates.py +46 -0
- rocket_engine/project/canvas/validate.py +603 -0
- rocket_engine/project/edit/__init__.py +5 -0
- rocket_engine/project/edit/cas.py +87 -0
- rocket_engine/project/edit/transactions.py +282 -0
- rocket_engine/project/errors.py +16 -0
- rocket_engine/project/frontend13.py +685 -0
- rocket_engine/project/geometry/__init__.py +23 -0
- rocket_engine/project/geometry/ops.py +451 -0
- rocket_engine/project/geometry/snap.py +103 -0
- rocket_engine/project/ip/__init__.py +1 -0
- rocket_engine/project/ip/blocks.py +195 -0
- rocket_engine/project/ip/entry.schema.json +78 -0
- rocket_engine/project/ip/lifecycle.py +402 -0
- rocket_engine/project/ip/verbs.py +123 -0
- rocket_engine/project/layers/__init__.py +6 -0
- rocket_engine/project/layers/bindings.py +115 -0
- rocket_engine/project/layers/catalog.py +228 -0
- rocket_engine/project/layers/presets.py +43 -0
- rocket_engine/project/ledger/__init__.py +1 -0
- rocket_engine/project/ledger/docs.py +167 -0
- rocket_engine/project/ledger/entities.py +176 -0
- rocket_engine/project/ledger/events.py +29 -0
- rocket_engine/project/ledger/runs.py +257 -0
- rocket_engine/project/ledger/shared.py +174 -0
- rocket_engine/project/ledger/snapshots.py +385 -0
- rocket_engine/project/ledger/wires.py +552 -0
- rocket_engine/project/process/__init__.py +14 -0
- rocket_engine/project/process/model.py +193 -0
- rocket_engine/project/process/recipes.py +121 -0
- rocket_engine/project/schematic/__init__.py +1 -0
- rocket_engine/project/schematic/hierarchy.py +312 -0
- rocket_engine/project/schematic/instances.py +393 -0
- rocket_engine/project/schematic/sheets.py +198 -0
- rocket_engine/project/service.py +2941 -0
- rocket_engine/project/symbols/__init__.py +1 -0
- rocket_engine/project/symbols/cells.py +324 -0
- rocket_engine/project/symbols/linkage.py +356 -0
- rocket_engine/project/symbols/symbols.py +352 -0
- rocket_engine/project/templates/__init__.py +303 -0
- rocket_engine/project/templates/errors.py +25 -0
- rocket_engine/project/testbench/__init__.py +49 -0
- rocket_engine/project/testbench/blocks.py +619 -0
- rocket_engine/project/testbench/templates.py +134 -0
- rocket_engine/stores/README.md +12 -0
- rocket_engine/stores/__init__.py +6 -0
- rocket_engine/stores/ecs.py +1508 -0
- rocket_engine/stores/layers.py +1023 -0
- rocket_engine/stores/settings.py +1188 -0
- rocket_engine/stores/wireplace.py +1324 -0
- rocket_engine/workflows/README.md +18 -0
- rocket_engine/workflows/__init__.py +53 -0
- rocket_engine/workflows/capability_plan.py +102 -0
- rocket_engine/workflows/completion.py +228 -0
- rocket_engine/workflows/connectgraph.py +734 -0
- rocket_engine/workflows/connectgraph_emit.py +336 -0
- rocket_engine/workflows/gates.py +144 -0
- rocket_engine/workflows/photonic_wire.py +76 -0
- rocket_engine/workflows/stages.py +324 -0
- rocket_engine/workflows/viewsync.py +399 -0
- rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
- rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
- rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
- rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
- rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""Raw asset serving (API-GENERALIZE/I29): verbatim PDK bytes, hash-checked.
|
|
2
|
+
|
|
3
|
+
The engine serves PDK store bytes exactly as they sit on disk — a
|
|
4
|
+
single `.sym` as-is, metadata listings, bulk manifests — with no
|
|
5
|
+
conversion and no rendering; parsing and styling stay frontend-side.
|
|
6
|
+
Every serve carries hash-checked identity (sha256 over the raw bytes),
|
|
7
|
+
and every path goes through PATH-GUARD containment, so escapes and
|
|
8
|
+
missing files fail loudly instead of leaking neighboring bytes.
|
|
9
|
+
|
|
10
|
+
Transport note: `serve_asset` base64-encodes the verbatim bytes for
|
|
11
|
+
JSON duplex channels; the sha256 is computed over the raw bytes, so
|
|
12
|
+
decoding and re-hashing always reproduces the served identity.
|
|
13
|
+
|
|
14
|
+
PRD aliases: the PRD names `list_pdk_assets`/`get_pdk_asset` are
|
|
15
|
+
thin wrappers over `list_assets`/`serve_asset` with identical
|
|
16
|
+
payloads — the wire always carries the PDK's own identifiers, and
|
|
17
|
+
humans and agents may query by either spelling. The canonical
|
|
18
|
+
functions stay the transport names until the adapt lane catalogs
|
|
19
|
+
the aliases in the vocabulary and composition wiring (see
|
|
20
|
+
`PRD_ALIASES`); this module pins the alias behavior meanwhile.
|
|
21
|
+
|
|
22
|
+
Serving guards (MC-43): single-asset serves refuse over
|
|
23
|
+
`max_bytes` with `too_large` instead of encoding unbounded bulk,
|
|
24
|
+
and bulk listings page with `limit`/`cursor` following the v1
|
|
25
|
+
Page convention (`next_cursor`, `has_more`-style termination via
|
|
26
|
+
a null cursor).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import base64
|
|
30
|
+
import hashlib
|
|
31
|
+
|
|
32
|
+
from ruamel.yaml import YAML
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
from .errors import ContractError
|
|
36
|
+
from .paths import join_contained, slugify
|
|
37
|
+
from .pdks import resolve_engine_root
|
|
38
|
+
except ImportError: # standalone verification before install
|
|
39
|
+
from rocket_engine.interfaces.errors import ContractError
|
|
40
|
+
from rocket_engine.interfaces.paths import join_contained, slugify
|
|
41
|
+
from rocket_engine.interfaces.pdks import resolve_engine_root
|
|
42
|
+
|
|
43
|
+
ASSET_VERSION = 1
|
|
44
|
+
_HASH_CHUNK_BYTES = 1024 * 1024
|
|
45
|
+
|
|
46
|
+
#: Largest single asset `serve_asset` will encode; larger files
|
|
47
|
+
#: refuse with `too_large` so a blind read never swallows bulk.
|
|
48
|
+
MAX_SERVE_BYTES = 4 * 1024 * 1024
|
|
49
|
+
|
|
50
|
+
#: Largest single `list_assets` page; larger limits refuse loudly.
|
|
51
|
+
MAX_LIST_LIMIT = 1000
|
|
52
|
+
|
|
53
|
+
#: PRD-name alias -> canonical transport name. The adapt lane
|
|
54
|
+
#: catalogs these in the vocabulary and composition wiring; until
|
|
55
|
+
#: then transports serve the canonical names and this table pins
|
|
56
|
+
#: the alias contract at the module layer.
|
|
57
|
+
PRD_ALIASES = {
|
|
58
|
+
"list_pdk_assets": "list_assets",
|
|
59
|
+
"get_pdk_asset": "serve_asset",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_yaml = YAML(typ="safe")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _sha256_file(path):
|
|
66
|
+
digest = hashlib.sha256()
|
|
67
|
+
with open(path, "rb") as handle:
|
|
68
|
+
for chunk in iter(lambda: handle.read(_HASH_CHUNK_BYTES), b""):
|
|
69
|
+
digest.update(chunk)
|
|
70
|
+
return digest.hexdigest()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _pdk_dir(engine_root, pdk_id, revision):
|
|
74
|
+
root = resolve_engine_root(engine_root)
|
|
75
|
+
try:
|
|
76
|
+
return join_contained(root, "pdks", slugify(pdk_id), revision)
|
|
77
|
+
except Exception as exc:
|
|
78
|
+
raise ContractError(
|
|
79
|
+
"invalid_reference", f"Unknown PDK: {pdk_id} {revision}."
|
|
80
|
+
) from exc
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _display_id(pdk_dir, pdk_id):
|
|
84
|
+
manifest = pdk_dir / "manifest.yaml"
|
|
85
|
+
try:
|
|
86
|
+
text = manifest.read_text(encoding="utf-8")
|
|
87
|
+
except OSError:
|
|
88
|
+
return slugify(pdk_id)
|
|
89
|
+
try:
|
|
90
|
+
doc = _yaml.load(text)
|
|
91
|
+
except Exception:
|
|
92
|
+
return slugify(pdk_id)
|
|
93
|
+
if not isinstance(doc, dict):
|
|
94
|
+
return slugify(pdk_id)
|
|
95
|
+
return str(doc.get("display_id") or doc.get("id") or slugify(pdk_id))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _canon_relpath(relpath):
|
|
99
|
+
if not isinstance(relpath, str):
|
|
100
|
+
raise ContractError(
|
|
101
|
+
"invalid_reference",
|
|
102
|
+
f"Asset path must be text, got {relpath!r}.",
|
|
103
|
+
)
|
|
104
|
+
unix = relpath.replace("\\", "/")
|
|
105
|
+
if unix == "" or unix.startswith("/") or unix.endswith("/"):
|
|
106
|
+
raise ContractError(
|
|
107
|
+
"invalid_reference", f"Bad asset path: {relpath!r}."
|
|
108
|
+
)
|
|
109
|
+
parts = [part for part in unix.split("/") if part not in ("", ".")]
|
|
110
|
+
if not parts or ".." in parts:
|
|
111
|
+
raise ContractError(
|
|
112
|
+
"invalid_reference", f"Asset path escapes: {relpath!r}."
|
|
113
|
+
)
|
|
114
|
+
return "/".join(parts)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _scan_files(pdk_dir):
|
|
118
|
+
"""Sorted (relpath, sha256, size) for regular files; symlinks named."""
|
|
119
|
+
files = []
|
|
120
|
+
symlinks = []
|
|
121
|
+
for target in sorted(pdk_dir.rglob("*")):
|
|
122
|
+
if not target.is_symlink() and not target.is_file():
|
|
123
|
+
continue
|
|
124
|
+
try:
|
|
125
|
+
rel = target.relative_to(pdk_dir).as_posix()
|
|
126
|
+
except ValueError:
|
|
127
|
+
continue
|
|
128
|
+
if target.is_symlink():
|
|
129
|
+
try:
|
|
130
|
+
resolves = target.resolve().is_relative_to(pdk_dir.resolve())
|
|
131
|
+
except OSError:
|
|
132
|
+
resolves = False
|
|
133
|
+
symlinks.append({"path": rel, "resolves": resolves})
|
|
134
|
+
continue
|
|
135
|
+
files.append(
|
|
136
|
+
{"path": rel, "sha256": _sha256_file(target),
|
|
137
|
+
"size": target.stat().st_size}
|
|
138
|
+
)
|
|
139
|
+
files.sort(key=lambda e: e["path"])
|
|
140
|
+
symlinks.sort(key=lambda e: e["path"])
|
|
141
|
+
return files, symlinks
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _check_max_bytes(max_bytes):
|
|
145
|
+
if max_bytes is None:
|
|
146
|
+
return None
|
|
147
|
+
if (
|
|
148
|
+
isinstance(max_bytes, bool)
|
|
149
|
+
or not isinstance(max_bytes, int)
|
|
150
|
+
or max_bytes <= 0
|
|
151
|
+
):
|
|
152
|
+
raise ContractError(
|
|
153
|
+
"invalid_command",
|
|
154
|
+
f"max_bytes must be a positive int, got {max_bytes!r}.",
|
|
155
|
+
)
|
|
156
|
+
return max_bytes
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def serve_asset(pdk_id, revision, path, engine_root=None,
|
|
160
|
+
max_bytes=MAX_SERVE_BYTES):
|
|
161
|
+
"""Serve one PDK file's bytes verbatim with hash-checked identity.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
pdk_id: PDK id (either display spelling resolves).
|
|
165
|
+
revision: PDK revision pin.
|
|
166
|
+
path: Store-relative file path (single `.sym` as-is, any
|
|
167
|
+
depth).
|
|
168
|
+
engine_root: Explicit store root; resolved when omitted.
|
|
169
|
+
max_bytes: Largest encodable size; larger files refuse
|
|
170
|
+
with too_large instead of encoding bulk. None opts
|
|
171
|
+
out (bulk reads belong to selective calls, never a
|
|
172
|
+
blind loop).
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Mapping with asset_version, pdk_id, display_id, revision,
|
|
176
|
+
canonical path, size, sha256 over the raw bytes, and
|
|
177
|
+
the verbatim bytes base64-encoded for JSON channels.
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
ContractError: invalid_reference for an unknown PDK, a
|
|
181
|
+
missing file, a directory path, or an escaping path;
|
|
182
|
+
invalid_command for a malformed max_bytes; too_large
|
|
183
|
+
when the file exceeds max_bytes.
|
|
184
|
+
"""
|
|
185
|
+
cap = _check_max_bytes(max_bytes)
|
|
186
|
+
pdk_dir = _pdk_dir(engine_root, pdk_id, revision)
|
|
187
|
+
if not pdk_dir.is_dir():
|
|
188
|
+
raise ContractError(
|
|
189
|
+
"invalid_reference", f"Unknown PDK: {pdk_id} {revision}."
|
|
190
|
+
)
|
|
191
|
+
canonical = _canon_relpath(path)
|
|
192
|
+
try:
|
|
193
|
+
target = join_contained(pdk_dir, *canonical.split("/"))
|
|
194
|
+
except Exception as exc:
|
|
195
|
+
raise ContractError(
|
|
196
|
+
"invalid_reference", f"Asset path escapes: {path!r}."
|
|
197
|
+
) from exc
|
|
198
|
+
if not target.is_file() or target.is_symlink():
|
|
199
|
+
# Symlinks resolve through list_assets metadata, never here:
|
|
200
|
+
# serving follows no link the caller cannot see.
|
|
201
|
+
raise ContractError(
|
|
202
|
+
"invalid_reference", f"No such asset: {path!r}."
|
|
203
|
+
)
|
|
204
|
+
size = target.stat().st_size
|
|
205
|
+
if cap is not None and size > cap:
|
|
206
|
+
raise ContractError(
|
|
207
|
+
"too_large",
|
|
208
|
+
f"Asset {canonical!r} is {size} bytes, over "
|
|
209
|
+
f"max_bytes={cap}; narrow with list_assets prefix "
|
|
210
|
+
"filtering and serve selectively, never in a blind loop.",
|
|
211
|
+
)
|
|
212
|
+
raw = target.read_bytes()
|
|
213
|
+
return {
|
|
214
|
+
"asset_version": ASSET_VERSION,
|
|
215
|
+
"pdk_id": slugify(pdk_id),
|
|
216
|
+
"display_id": _display_id(pdk_dir, pdk_id),
|
|
217
|
+
"revision": revision,
|
|
218
|
+
"path": canonical,
|
|
219
|
+
"size": len(raw),
|
|
220
|
+
"sha256": hashlib.sha256(raw).hexdigest(),
|
|
221
|
+
"content_b64": base64.b64encode(raw).decode("ascii"),
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def get_pdk_asset(pdk_id, revision, path, engine_root=None,
|
|
226
|
+
max_bytes=MAX_SERVE_BYTES):
|
|
227
|
+
"""Serve one PDK file verbatim under its PRD name.
|
|
228
|
+
|
|
229
|
+
Alias for `serve_asset`: identical arguments, identical
|
|
230
|
+
payload — the wire always carries the PDK's own identifiers.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
pdk_id: PDK id (either display spelling resolves).
|
|
234
|
+
revision: PDK revision pin.
|
|
235
|
+
path: Store-relative file path.
|
|
236
|
+
engine_root: Explicit store root; resolved when omitted.
|
|
237
|
+
max_bytes: Largest encodable size; None opts out.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Serve mapping, exactly as `serve_asset` returns.
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
ContractError: Whatever `serve_asset` raises.
|
|
244
|
+
"""
|
|
245
|
+
return serve_asset(
|
|
246
|
+
pdk_id, revision, path, engine_root=engine_root,
|
|
247
|
+
max_bytes=max_bytes,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _check_limit(limit):
|
|
252
|
+
if limit is None:
|
|
253
|
+
return None
|
|
254
|
+
if (
|
|
255
|
+
isinstance(limit, bool)
|
|
256
|
+
or not isinstance(limit, int)
|
|
257
|
+
or not 1 <= limit <= MAX_LIST_LIMIT
|
|
258
|
+
):
|
|
259
|
+
raise ContractError(
|
|
260
|
+
"invalid_command",
|
|
261
|
+
f"limit must be an int 1..{MAX_LIST_LIMIT}, "
|
|
262
|
+
f"got {limit!r}.",
|
|
263
|
+
)
|
|
264
|
+
return limit
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _check_cursor(cursor, total):
|
|
268
|
+
if cursor is None:
|
|
269
|
+
return 0
|
|
270
|
+
if not isinstance(cursor, str) or not cursor.isdigit():
|
|
271
|
+
raise ContractError(
|
|
272
|
+
"invalid_command",
|
|
273
|
+
f"cursor must be an opaque page token from a prior "
|
|
274
|
+
f"listing, got {cursor!r}.",
|
|
275
|
+
)
|
|
276
|
+
start = int(cursor)
|
|
277
|
+
if start > total:
|
|
278
|
+
raise ContractError(
|
|
279
|
+
"invalid_command",
|
|
280
|
+
f"Unknown cursor {cursor!r} for {total} files.",
|
|
281
|
+
)
|
|
282
|
+
return start
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def list_assets(pdk_id, revision, prefix=None, engine_root=None,
|
|
286
|
+
limit=None, cursor=None):
|
|
287
|
+
"""List PDK file metadata with per-file hash identity.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
pdk_id: PDK id (either display spelling resolves).
|
|
291
|
+
revision: PDK revision pin.
|
|
292
|
+
prefix: Optional store-relative prefix filter; everything
|
|
293
|
+
when omitted.
|
|
294
|
+
engine_root: Explicit store root; resolved when omitted.
|
|
295
|
+
limit: Maximum file entries in this page; the whole
|
|
296
|
+
filtered set when omitted.
|
|
297
|
+
cursor: Opaque page token from a prior listing; the
|
|
298
|
+
first page when omitted.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Mapping with asset_version, pdk_id, display_id, revision,
|
|
302
|
+
sorted file entries for this page (path, size,
|
|
303
|
+
sha256), symlink entries (path, resolves, unpaged),
|
|
304
|
+
the total file count, and the paging echo (limit,
|
|
305
|
+
cursor, next_cursor; next_cursor is None on the last
|
|
306
|
+
page, following the v1 Page convention).
|
|
307
|
+
|
|
308
|
+
Raises:
|
|
309
|
+
ContractError: invalid_reference for an unknown PDK or an
|
|
310
|
+
escaping prefix; invalid_command for a malformed
|
|
311
|
+
limit or cursor.
|
|
312
|
+
"""
|
|
313
|
+
page_limit = _check_limit(limit)
|
|
314
|
+
pdk_dir = _pdk_dir(engine_root, pdk_id, revision)
|
|
315
|
+
if not pdk_dir.is_dir():
|
|
316
|
+
raise ContractError(
|
|
317
|
+
"invalid_reference", f"Unknown PDK: {pdk_id} {revision}."
|
|
318
|
+
)
|
|
319
|
+
want = ""
|
|
320
|
+
if prefix:
|
|
321
|
+
want = _canon_relpath(prefix.rstrip("/") or prefix)
|
|
322
|
+
files, symlinks = _scan_files(pdk_dir)
|
|
323
|
+
if want:
|
|
324
|
+
files = [
|
|
325
|
+
entry for entry in files
|
|
326
|
+
if entry["path"] == want
|
|
327
|
+
or entry["path"].startswith(want + "/")
|
|
328
|
+
]
|
|
329
|
+
symlinks = [
|
|
330
|
+
entry for entry in symlinks
|
|
331
|
+
if entry["path"] == want
|
|
332
|
+
or entry["path"].startswith(want + "/")
|
|
333
|
+
]
|
|
334
|
+
start = _check_cursor(cursor, len(files))
|
|
335
|
+
if page_limit is None:
|
|
336
|
+
page = files
|
|
337
|
+
next_cursor = None
|
|
338
|
+
else:
|
|
339
|
+
page = files[start:start + page_limit]
|
|
340
|
+
ahead = start + len(page)
|
|
341
|
+
next_cursor = str(ahead) if ahead < len(files) else None
|
|
342
|
+
return {
|
|
343
|
+
"asset_version": ASSET_VERSION,
|
|
344
|
+
"pdk_id": slugify(pdk_id),
|
|
345
|
+
"display_id": _display_id(pdk_dir, pdk_id),
|
|
346
|
+
"revision": revision,
|
|
347
|
+
"files": page,
|
|
348
|
+
"symlinks": symlinks,
|
|
349
|
+
"count": len(files),
|
|
350
|
+
"limit": page_limit,
|
|
351
|
+
"cursor": cursor,
|
|
352
|
+
"next_cursor": next_cursor,
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def list_pdk_assets(pdk_id, revision, prefix=None, engine_root=None,
|
|
357
|
+
limit=None, cursor=None):
|
|
358
|
+
"""List PDK file metadata under its PRD name.
|
|
359
|
+
|
|
360
|
+
Alias for `list_assets`: identical arguments, identical
|
|
361
|
+
payload — the wire always carries the PDK's own identifiers.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
pdk_id: PDK id (either display spelling resolves).
|
|
365
|
+
revision: PDK revision pin.
|
|
366
|
+
prefix: Optional store-relative prefix filter.
|
|
367
|
+
engine_root: Explicit store root; resolved when omitted.
|
|
368
|
+
limit: Maximum file entries in this page.
|
|
369
|
+
cursor: Opaque page token from a prior listing.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Listing mapping, exactly as `list_assets` returns.
|
|
373
|
+
|
|
374
|
+
Raises:
|
|
375
|
+
ContractError: Whatever `list_assets` raises.
|
|
376
|
+
"""
|
|
377
|
+
return list_assets(
|
|
378
|
+
pdk_id, revision, prefix=prefix, engine_root=engine_root,
|
|
379
|
+
limit=limit, cursor=cursor,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def asset_manifest(pdk_id, revision, prefix=None, engine_root=None):
|
|
384
|
+
"""Build a bulk manifest with tree-level hash identity.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
pdk_id: PDK id (either display spelling resolves).
|
|
388
|
+
revision: PDK revision pin.
|
|
389
|
+
prefix: Optional store-relative prefix filter; the whole
|
|
390
|
+
tree when omitted.
|
|
391
|
+
engine_root: Explicit store root; resolved when omitted.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Listing mapping plus tree_sha256 over the sorted
|
|
395
|
+
`sha256:path` lines — one hash pinning the bulk bytes.
|
|
396
|
+
|
|
397
|
+
Raises:
|
|
398
|
+
ContractError: invalid_reference for an unknown PDK or an
|
|
399
|
+
escaping prefix.
|
|
400
|
+
"""
|
|
401
|
+
listing = list_assets(
|
|
402
|
+
pdk_id, revision, prefix=prefix, engine_root=engine_root
|
|
403
|
+
)
|
|
404
|
+
digest = hashlib.sha256()
|
|
405
|
+
for entry in listing["files"]:
|
|
406
|
+
digest.update(f"{entry['sha256']}:{entry['path']}\n".encode("utf-8"))
|
|
407
|
+
return {**listing, "tree_sha256": digest.hexdigest()}
|