File size: 15,264 Bytes
c10c824
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
"""Export the learning-contract report sets as compact JSON for the observatory's Learning view.

Reads only JSON and Markdown under ``docs/research/results/contract-2026-09-23/``; never weights. Writes one
``index.json`` to ``docs/research/results/learning-observatory/`` and mirrors the same bytes into
``dashboard/public/assets/learning/``, which the dashboard build copies into ``dist/assets`` (the static path the Space
serves). The mirror is a sibling of the Sleep export's ``assets/observatory/``, whose own mirror step replaces that whole
directory; the Sleep export is not touched.

Two kinds of set are recognized by content, not by name:

* a **report set** (``scripts/experiments/transfer_contract.py``): ``manifest.json`` plus one JSON per baseline mode
  (``frozen``, ``continued``, ``in_context``). Its learner is ``DynamicsLearner``, whose off-intervention is
  ``beta_scale=0`` (writes disabled, decay active).
* an **ablation set** (``scripts/experiments/coordinate_ablation.py``): one JSON per variant carrying ``variant``,
  ``contract`` and its own ``no_adapt_label``. Variants not archived are listed as missing, in the runner's order.

Every value is copied from a source file or computed from it, never invented. Missing values stay ``null``; a rate over
an empty set is ``null``, not zero. A set measured under a contract version other than the current
``plastic.eval.contract.CONTRACT_VERSION`` is marked ``current: false`` (the only staleness the page can show; staleness
of this export against its sources is what ``--check`` reports). Output is deterministic: no wall-clock time, no
absolute paths; it is identified by the sha256 of each source, an archive digest over them, and ``EXPORTER_VERSION``.

Usage
  python -m scripts.export_contract_observatory            # refresh from the archive
  python -m scripts.export_contract_observatory --check    # exit 1 if the committed export or its mirror is stale
"""

from __future__ import annotations

import argparse
import hashlib
import re
import shutil
import sys
import tempfile
from pathlib import Path
from typing import Any

from plastic.eval.contract import CONTRACT_VERSION
from scripts.experiments.coordinate_ablation import VARIANTS
from scripts.export_sleep_observatory import dump, mirror, num, read_json, sha256, tree_bytes

EXPORTER_VERSION = "1.0.0"
SCHEMA = "plastic.learning-observatory/1"
ROOT = Path(__file__).resolve().parent.parent
ARCHIVE = ROOT / "docs" / "research" / "results" / "contract-2026-09-23"
OUT = ROOT / "docs" / "research" / "results" / "learning-observatory"
MIRROR = ROOT / "dashboard" / "public" / "assets" / "learning"

MODES = ("frozen", "continued", "in_context")
# The off-intervention of each learner class, as the learner implements it (plastic/eval/contract.py DynamicsLearner:
# adapt=False runs the model with beta_scale=0). Ablation variants record their own label; this is used only for report
# sets, whose producer always builds a DynamicsLearner. tests/ pin it against coordinate_ablation.learner_for.
DYNAMICS_NO_ADAPT = "writes disabled (beta_scale=0)"


# ---------------------------------------------------------------------------------------------------- pieces
def speed_view(s: dict[str, Any] | None) -> dict[str, Any] | None:
    """Adaptation speed. The contract reports ``steps_to_half`` equal to the horizon when the ratio never drops below
    one half; that case is exported as ``reached_half: false`` and ``steps_to_half: null``, not as a step."""
    if not s:
        return None
    curve = [v for v in s.get("curve") or [] if isinstance(v, (int, float))]
    reached = any(v < 0.5 for v in curve) if curve else None
    return {"mean_ratio": num(s.get("area")), "steps_to_half": s.get("steps_to_half") if reached is not False else None,
            "reached_half": reached, "horizon": len(curve) or None, "episodes": s.get("episodes")}


def pair(block: dict[str, Any] | None) -> dict[str, Any]:
    block = block or {}
    return {"adapt": num(block.get("adapt")), "no_adapt": num(block.get("no_adapt")), "elements": block.get("elements")}


def before_view(rep: dict[str, Any]) -> dict[str, Any]:
    """The before-stream block: held-out pairings per held-out policy, the training distribution, adaptation speed."""
    transfer = rep.get("transfer") or {}
    return {
        "policies": [{"policy": p, **pair(v.get("before"))} for p, v in transfer.items()],
        "train": {"policies": list((rep.get("spec") or {}).get("train_policies") or []), **pair((rep.get("forgetting") or {}).get("before"))},
        "speed": speed_view((rep.get("speed") or {}).get("before")),
    }


def rate(value: Any, n: Any) -> float | None:
    """A rate over an empty set is undefined (null), never zero."""
    return num(value) if isinstance(n, int) and n > 0 else None


def mode_view(rep: dict[str, Any]) -> dict[str, Any]:
    c = rep.get("correction") or {}
    acc = rep.get("acceptance") or {}
    comp = rep.get("compute") or {}
    rev = rep.get("revert") or {}
    return {
        "mode": rep.get("mode"),
        "transfer": [{"policy": p, "delta": num(v.get("delta_mse")), "delta_no_adapt": num(v.get("delta_mse_no_adapt"))}
                     for p, v in (rep.get("transfer") or {}).items()],
        "forgetting_delta": num((rep.get("forgetting") or {}).get("delta_mse")),
        "poison_harm_vs_clean": num(c.get("harm")), "poison_harm_vs_start": num(c.get("harm_vs_before")),
        "correction_residual": num(c.get("residual")), "correction_residual_vs_start": num(c.get("residual_vs_before")),
        "revert": {"ok": rev.get("ok") if isinstance(rev.get("ok"), bool) else None, "gap": num(rev.get("gap")), "tolerance": num(rev.get("tolerance"))},
        "acceptance": {"accepted_good": rate(acc.get("accepted_good"), acc.get("n_good")), "n_good": acc.get("n_good"),
                       "refused_bad": rate(acc.get("refused_bad"), acc.get("n_bad")), "n_bad": acc.get("n_bad")},
        "decisions": [{k: d.get(k) for k in ("stream", "beneficial", "accepted")} for d in rep.get("decisions") or []],
        "tokens_consumed": comp.get("tokens_consumed"), "tokens_measured": comp.get("tokens_measured"),
        "tokens_measured_without_context": comp.get("tokens_measured_without_context"), "parameters": comp.get("parameters"),
    }


def readme_tags(archive: Path) -> dict[str, str]:
    """``## `set_id` (tag)`` headings of the archive README: the short name each set is published under."""
    readme = archive / "README.md"
    if not readme.exists():
        return {}
    return {m.group(1): m.group(2).strip() for m in re.finditer(r"^##\s+`([^`]+)`\s*\(([^)]+)\)", readme.read_text(encoding="utf-8"), re.M)}


def window_view(w: dict[str, Any] | None) -> dict[str, Any]:
    w = w or {}
    return {"update_period": w.get("update_period"), "boundaries_per_episode": w.get("boundaries_per_episode"), "checked": bool(w.get("checked"))}


def source_list(paths: list[Path], archive: Path) -> list[dict[str, str]]:
    return [{"file": p.relative_to(archive).as_posix(), "sha256": sha256(p)} for p in sorted(paths)]


# ---------------------------------------------------------------------------------------------------- sets
def export_report_set(d: Path, archive: Path, tags: dict[str, str]) -> dict[str, Any]:
    m = read_json(d / "manifest.json")
    reports = {mode: read_json(d / f"{mode}.json") for mode in MODES if (d / f"{mode}.json").exists()}
    if not reports:
        raise ValueError(f"{d.name}: a manifest without any mode report")
    first = next(iter(reports.values()))
    for mode, rep in reports.items():
        for key, want in (("contract_version", m.get("contract_version")), ("mode", mode)):
            if rep.get(key) != want:
                raise ValueError(f"{d.name}/{mode}.json: {key} {rep.get(key)!r} differs from {want!r}")
        if (rep.get("split") or {}).get("id") != m.get("split_id") or window_view(rep.get("adaptation_window")) != window_view(m.get("adaptation_window")):
            raise ValueError(f"{d.name}/{mode}.json: split or adaptation window differs from the manifest")
        if before_view(rep) != before_view(first):
            raise ValueError(f"{d.name}/{mode}.json: the before-stream measurement differs between modes")
    split = first.get("split") or {}
    spec = first.get("spec") or {}
    version = m.get("contract_version")
    sources = [d / "manifest.json", *(d / f"{mode}.json" for mode in reports)] + sorted(d.glob("*.md"))
    return {
        "id": d.name, "kind": "report", "tag": tags.get(d.name),
        "checkpoint": {"model_id": m.get("model_id"), "digest_prefix": str(m.get("checkpoint_digest") or "")[:16] or None,
                       "step": (m.get("model_record") or {}).get("step")},
        "execution_commit": str(m.get("execution_commit") or "")[:12] or None, "device": m.get("device"), "seed": m.get("seed"),
        "contract_version": version, "current": version == CONTRACT_VERSION,
        "split": {"id": split.get("id"), "train": split.get("train"), "heldout": split.get("heldout"), "bound_to_learner": split.get("bound_to_learner")},
        "adaptation_window": window_view(m.get("adaptation_window")),
        "spec": {k: spec.get(k) for k in ("seq_len", "stream_episodes", "heldout_policies", "train_policies", "poison_bias", "revert_tolerance", "eval_batch", "probe_steps")},
        "stream": first.get("stream"), "learner": m.get("learner"),
        "no_adapt_label": DYNAMICS_NO_ADAPT,
        "before": before_view(first),
        "modes": [mode_view(reports[mode]) for mode in MODES if mode in reports],
        "missing_modes": [mode for mode in MODES if mode not in reports],
        "sources": source_list(sources, archive),
    }


def variant_view(r: dict[str, Any]) -> dict[str, Any]:
    c = r.get("contract") or {}
    log = (r.get("train") or {}).get("log") or []
    last = log[-1] if log else {}
    size = r.get("size") or {}
    version = c.get("contract_version")
    return {
        "variant": r.get("variant"), "config": r.get("config"), "parameters": size.get("parameters"),
        "size": {k: size.get(k) for k in ("d_model", "n_heads", "n_layers", "chunk")},
        "train_steps": (r.get("train") or {}).get("steps"), "s_per_step": num((r.get("train") or {}).get("s_per_step"), 4),
        "final_train_loss": num(last.get("loss"), 4),
        "eta_per_layer": [num(e, 4) for e in last["eta"]] if last.get("eta") else None,
        "no_adapt_label": r.get("no_adapt_label"),
        "contract_version": version, "current": version == CONTRACT_VERSION,
        "split_id": (c.get("split") or {}).get("id"), "adaptation_window": window_view(c.get("adaptation_window")),
        "before": before_view(c),
    }


def export_ablation_set(d: Path, archive: Path, tags: dict[str, str], files: list[Path]) -> dict[str, Any]:
    results = {read_json(p)["variant"]: (p, read_json(p)) for p in files}
    manifest = read_json(d / "manifest.json") if (d / "manifest.json").exists() else {}
    variants = [variant_view(results[v][1]) for v in VARIANTS if v in results]
    variants += [variant_view(r) for v, (_, r) in sorted(results.items()) if v not in VARIANTS]
    versions = sorted({v["contract_version"] for v in variants if v["contract_version"]})
    sources = [p for p, _ in results.values()] + [d / "manifest.json"] * bool(manifest) + sorted(d.glob("*.md"))
    return {
        "id": d.name, "kind": "ablation", "tag": tags.get(d.name),
        "execution_commit": str(manifest.get("execution_commit") or "")[:12] or None,
        "contract_version": versions[0] if len(versions) == 1 else None, "contract_versions": versions,
        "current": bool(variants) and all(v["current"] for v in variants),
        "variants": variants, "missing": [v for v in VARIANTS if v not in results],
        "sources": source_list(sources, archive),
    }


def classify(d: Path) -> tuple[str, list[Path]] | None:
    """A report set has a manifest and at least one mode report; an ablation set has variant results."""
    if (d / "manifest.json").exists() and any((d / f"{mode}.json").exists() for mode in MODES):
        return "report", []
    variants = [p for p in sorted(d.glob("*.json")) if p.name != "manifest.json" and {"variant", "contract"} <= set(read_json(p))]
    return ("ablation", variants) if variants else None


# ---------------------------------------------------------------------------------------------------- driver
def build(out: Path, archive: Path = ARCHIVE) -> dict[str, Any]:
    tags = readme_tags(archive)
    sets = []
    for d in sorted(p for p in archive.iterdir() if p.is_dir()) if archive.exists() else []:
        kind = classify(d)
        if kind is None:
            continue
        sets.append(export_report_set(d, archive, tags) if kind[0] == "report" else export_ablation_set(d, archive, tags, kind[1]))
    digests = sorted({(f"{s['id']}/{src['file']}", src["sha256"]) for s in sets for src in s["sources"]})
    try:
        archive_name = archive.resolve().relative_to(ROOT).as_posix()
    except ValueError:
        archive_name = archive.name
    index = {
        "schema": SCHEMA, "exporter_version": EXPORTER_VERSION, "current_contract_version": CONTRACT_VERSION,
        "archive": archive_name, "archive_digest": hashlib.sha256("".join(f"{f}:{h}\n" for f, h in digests).encode()).hexdigest(),
        "sets": sets,
    }
    out.mkdir(parents=True, exist_ok=True)
    (out / "index.json").write_text(dump(index), encoding="utf-8")
    return index


def main(argv: list[str] | None = None) -> int:
    ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
    ap.add_argument("--out", type=Path, default=OUT, help="export directory (default: docs/research/results/learning-observatory)")
    ap.add_argument("--mirror", type=Path, default=MIRROR, help="dashboard copy served under /assets (default: dashboard/public/assets/learning)")
    ap.add_argument("--no-mirror", action="store_true", help="write the export only")
    ap.add_argument("--check", action="store_true", help="rebuild into a temporary directory and exit 1 if the committed export or its mirror differs")
    args = ap.parse_args(argv)
    if args.check:
        with tempfile.TemporaryDirectory() as tmp:
            work = Path(tmp) / "export"
            build(work)
            fresh = tree_bytes(work)
            committed = tree_bytes(args.out) if args.out.exists() else {}
            mirrored = tree_bytes(args.mirror) if args.mirror.exists() else {}
            stale = sorted(k for k in set(fresh) | set(committed) if fresh.get(k) != committed.get(k))
            drift = sorted(k for k in set(committed) | set(mirrored) if committed.get(k) != mirrored.get(k))
            for k in stale:
                print(f"stale: {k}")
            for k in drift:
                print(f"mirror differs: {k}")
            return 1 if stale or drift else 0
    if args.out.exists():
        shutil.rmtree(args.out)
    index = build(args.out)
    if not args.no_mirror:
        mirror(args.out, args.mirror)
    kinds = [s["kind"] for s in index["sets"]]
    print(f"exported {kinds.count('report')} report sets and {kinds.count('ablation')} ablation sets -> {args.out}")
    return 0


if __name__ == "__main__":
    sys.exit(main())