tenseleyflow/sway / 96a3438

Browse files

README + CHANGELOG: Sprint 26 X3 — sway pack / sway unpack

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
96a3438adaeb713ae7e6746ea7fdd5c604c9c4ff
Parents
3e0e0ce
Tree
e3a3df0

2 changed files

StatusFile+-
M CHANGELOG.md 62 0
M README.md 36 0
CHANGELOG.mdmodified
@@ -2,6 +2,68 @@
22
 
33
 ## Unreleased
44
 
5
+### Sprint 26 — X3 sway pack / unpack (sway-side half of the cross-repo X1+X3 pair)
6
+
7
+Closes the X3 half of Audit 03's "make a sway run reproducible by a
8
+coworker without recreating their environment" goal. The X1 half
9
+(`dlm export --to sway-json`) lands in the dlm repo via a separate
10
+PR; this changelog block ships the sway-only deliverable.
11
+
12
+**New CLI commands.**
13
+
14
+- **`sway pack <spec> [-o OUT] [--include-golden PATH]
15
+  [--include-null-cache/--no-include-null-cache] [--max-size-mb MB]`** —
16
+  bundles the spec + its `dlm_source` (when set) + cached null-stats
17
+  entries + an optional last-known-good JSON report into a single
18
+  `*.swaypack.tar.gz`. Default cap 50 MB; refuses to overwrite.
19
+- **`sway unpack <pack> [-o DIR]`** — extracts into `<DIR>/swaypack/`,
20
+  validates the manifest, and prints the ready-to-run `sway run`
21
+  invocation including the `SWAY_NULL_CACHE_DIR=...` env var that
22
+  redirects null-stats lookups at the bundled cache.
23
+
24
+**Pack format (`swaypack_version=1`).**
25
+
26
+```
27
+swaypack/
28
+  manifest.json    # version + counts + packed_at + sway_version
29
+  sway.yaml        # the spec, verbatim
30
+  source.dlm       # spec.dlm_source content (when present)
31
+  null-stats/      # one .json per cache key
32
+  golden.json      # known-good run report (when --include-golden)
33
+```
34
+
35
+Implementation modules:
36
+- **`cli/_pack.py`** — builds the tarball in-memory first so the
37
+  size cap can refuse cleanly *before* writing a half-built pack
38
+  to disk. Path-traversal-safe by construction (we author every
39
+  arcname).
40
+- **`cli/_unpack.py`** — uses `tarfile.extractall(filter='data')`
41
+  to reject absolute paths / `../` escapes / device files (3.11
42
+  compat). Validates `swaypack_version`; rejects mismatches with
43
+  a clear message.
44
+
45
+**Null-cache override env var.**
46
+
47
+- **`probes/_null_cache._cache_root`** now honors
48
+  `$SWAY_NULL_CACHE_DIR` if set (used verbatim — no
49
+  `dlm-sway/null-stats` suffix). Order: env override → XDG cache →
50
+  `~/.dlm-sway/null-stats`. The `sway unpack` CLI prints the exact
51
+  invocation that wires this env at the bundled cache.
52
+
53
+**Tests.**
54
+
55
+- **17 unit tests** in `tests/unit/test_pack_unpack.py`: round-trip
56
+  identity, manifest fields, dlm_source bundling (present + missing
57
+  + warning emit), golden bundling, every PackError + UnpackError
58
+  branch (overwrite refusal, size cap, missing file, corrupt
59
+  tarball, missing manifest, version mismatch, malformed root).
60
+- **2 slow+online integration tests** in
61
+  `tests/integration/test_pack_run_roundtrip.py`: pack → unpack →
62
+  `sway run` produces an identical band + per-probe verdict +
63
+  score; null-cache packing populates the unpack report's
64
+  `null_stats_dir` pointer.
65
+- 708 unit tests pass; mypy + ruff + format clean.
66
+
567
 ### Sprint 25 — P3 gradient_ghost probe (pre-run, cross-repo)
668
 
769
 New zero-forward-pass diagnostic probe that loads dlm's
README.mdmodified
@@ -331,6 +331,42 @@ sway run sway.yaml
331331
 Per-section attribution tells you *which* parts of your document
332332
 actually moved the model — a kind of signal no other tool provides.
333333
 
334
+> Heads up — once dlm publishes its sister change, `dlm export --to
335
+> sway-json` writes a ready-to-run `sway.yaml` next to the GGUF.
336
+> Skip the manual `sway autogen` step entirely.
337
+
338
+## Reproducing a sway run
339
+
340
+Sometimes you want a coworker (or a future-you, or a bug report) to
341
+re-execute exactly what you ran without recreating your environment.
342
+`sway pack` bundles the spec + the source `.dlm` document + your
343
+locally-cached null-stats + (optionally) a known-good report into a
344
+single `*.swaypack.tar.gz` you can email or commit:
345
+
346
+```bash
347
+sway pack sway.yaml -o audit-2026-04.swaypack.tar.gz \
348
+  --include-golden last-run.json
349
+# wrote audit-2026-04.swaypack.tar.gz (4.2 KB)  sections=312b  null_stats=0  golden=yes
350
+
351
+# share the tarball with a coworker
352
+sway unpack audit-2026-04.swaypack.tar.gz
353
+# extracted: <cwd>/swaypack
354
+#   spec_path: <cwd>/swaypack/sway.yaml
355
+#   ...
356
+# To run the bundled spec:
357
+#   SWAY_NULL_CACHE_DIR=<cwd>/swaypack/null-stats sway run <cwd>/swaypack/sway.yaml
358
+```
359
+
360
+The packed null-stats cache means the consumer doesn't pay the
361
+re-calibration cost (~120 forward passes on a typical 10-probe
362
+suite). The unpack output prints the exact `SWAY_NULL_CACHE_DIR=...`
363
+env var to use; that env redirects null-stats lookups at the bundled
364
+cache instead of `~/.dlm-sway/`.
365
+
366
+A bundled golden JSON makes it trivial for the consumer to verify
367
+their re-run matches yours. Defaults to a 50 MB pack-size cap;
368
+override with `--max-size-mb`.
369
+
334370
 ## Status
335371
 
336372
 Pre-alpha. API will break. Not yet on PyPI — install editable from source