Skip to content

Commit e7210c2

Browse files
authored
Merge pull request #164 from interscript/publish-canonical-names
fix(publish): canonical artifact filenames from zip metadata
2 parents c2386b7 + f155603 commit e7210c2

2 files changed

Lines changed: 52 additions & 6 deletions

File tree

‎scripts/publish_model.py‎

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import os
2020
import re
2121
import subprocess
22+
import shutil
2223
import sys
2324
import tempfile
2425
import zipfile
@@ -39,6 +40,14 @@
3940
DEFAULT_REPO = "interscript/interscript-ml"
4041

4142

43+
def canonical_filename(meta: dict) -> str:
44+
"""Published filenames derive from the zip's own metadata (id +
45+
precision), never from the local staging path — a /tmp name like
46+
ld-int4.zip must not leak into the index entry or asset name: the
47+
runtime resolver matches volume files by this pattern."""
48+
return f"{meta['id']}-{meta['precision']}.zip"
49+
50+
4251
def run(cmd: list[str], **kwargs) -> subprocess.CompletedProcess:
4352
return subprocess.run(cmd, check=True, **kwargs)
4453

@@ -166,18 +175,26 @@ def main() -> None:
166175
raise SystemExit(f"metadata id {meta['id']!r} != requested {args.model_id!r}")
167176
key = args.key or args.model_id
168177

169-
whole_sha = sha256_file(args.zip)
170-
size = args.zip.stat().st_size
178+
canonical = canonical_filename(meta)
179+
zip_path = args.zip
180+
if zip_path.name != canonical:
181+
staged = Path(tempfile.mkdtemp(prefix="publish-")) / canonical
182+
shutil.copyfile(zip_path, staged)
183+
print(f"staging {zip_path.name} -> {canonical}")
184+
zip_path = staged
185+
186+
whole_sha = sha256_file(zip_path)
187+
size = zip_path.stat().st_size
171188

172-
assets = [args.zip]
189+
assets = [zip_path]
173190
if size > SPLIT_THRESHOLD:
174191
print(f"{size:,} bytes > {SPLIT_THRESHOLD:,}; splitting for the GitHub asset cap")
175-
parts = split_zip(args.zip, 1_500_000_000)
192+
parts = split_zip(zip_path, 1_500_000_000)
176193
assets = [part for part, _, _ in parts]
177194

178195
tag = args.model_id
179196
with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False) as fh:
180-
fh.write(release_notes(key, meta, args.zip.name, size, whole_sha, assets))
197+
fh.write(release_notes(key, meta, canonical, size, whole_sha, assets))
181198
notes_path = fh.name
182199

183200
existing = subprocess.run(
@@ -225,7 +242,7 @@ def main() -> None:
225242
# models/<family>/<id>.metadata.yaml, e.g. models/heb-diac/heb-diac-1.0...
226243
family = key.rsplit("-", 1)[0]
227244
upsert_models_yaml(wt_models, key,
228-
entry_block(key, meta, args.zip.name, whole_sha,
245+
entry_block(key, meta, canonical, whole_sha,
229246
size, assets, args.repo, tag))
230247
model_dir = worktree / "models" / family
231248
model_dir.mkdir(parents=True, exist_ok=True)

‎tests/test_publish_model.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Canonical artifact naming: the index entry and release asset name
2+
come from the zip's metadata, never the local staging path."""
3+
4+
from __future__ import annotations
5+
6+
import sys
7+
from pathlib import Path
8+
9+
import pytest
10+
11+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
12+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
13+
14+
pytest.importorskip("yaml")
15+
16+
from publish_model import canonical_filename # noqa: E402
17+
18+
19+
def test_filename_from_metadata() -> None:
20+
meta = {"id": "ara-diac-layerdrop-1.0", "precision": "int4"}
21+
assert canonical_filename(meta) == "ara-diac-layerdrop-1.0-int4.zip"
22+
23+
24+
def test_staging_path_cannot_leak() -> None:
25+
# a /tmp staging name like ld-int4.zip must never become the
26+
# published filename — the runtime resolver matches volume files
27+
# by the canonical pattern
28+
meta = {"id": "ara-diac-layerdrop-1.0", "precision": "fp32"}
29+
assert canonical_filename(meta) != "ld-fp32.zip"

0 commit comments

Comments
 (0)