SeaWolf-AI commited on
Commit
0e30b15
·
verified ·
1 Parent(s): d209e4f

Add decision core: autojev/sft_pipeline.py

Browse files
Files changed (1) hide show
  1. autojev/sft_pipeline.py +532 -0
autojev/sft_pipeline.py ADDED
@@ -0,0 +1,532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Freeze row order once; publish each audited training tranche atomically."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import gzip
7
+ import hashlib
8
+ import json
9
+ import os
10
+ import random
11
+ from collections import Counter, defaultdict
12
+ from collections.abc import Iterator, Sequence
13
+ from pathlib import Path
14
+ from typing import TypedDict, cast
15
+
16
+ from autojev.events import record
17
+ from autojev.sft_synthetic import COUNTS, Slot, load_plan
18
+ from autojev.types import Example, JSONValue
19
+
20
+ SEED = 20260921
21
+ TRANCHES = 10
22
+ NEW_ROWS = 5000
23
+ REPLAY_ROWS = 1920
24
+ TOTAL_ROWS = 69200
25
+
26
+
27
+ class Tranche(TypedDict):
28
+ index: int
29
+ synthetic_ids: list[str]
30
+ synthetic_families: list[str]
31
+ replay_ids: list[str]
32
+ ordered_ids: list[str]
33
+ data_path: str
34
+ synthetic_path: str
35
+ manifest_path: str
36
+ counts: dict[str, JSONValue]
37
+
38
+
39
+ class Schedule(TypedDict):
40
+ schema_version: int
41
+ seed: int
42
+ total_rows: int
43
+ ordered_ids: list[str]
44
+ plan_path: str
45
+ plan_sha256: str
46
+ original_train_path: str
47
+ original_train_sha256: str
48
+ replay_ids: list[str]
49
+ selected_replay_sha256: str
50
+ selected_replay_ids_sha256: str
51
+ public_calibration_path: str
52
+ public_calibration_sha256: str
53
+ calibration_path: str
54
+ calibration_ids: list[str]
55
+ controls_audit_path: str
56
+ synthetic_calibration_path: str
57
+ synthetic_confirmation_path: str
58
+ control_ids: dict[str, list[str]]
59
+ audit_order: list[str]
60
+ assignments: list[dict[str, JSONValue]]
61
+ code_sha256: dict[str, str]
62
+ tranches: list[Tranche]
63
+
64
+
65
+ class Receipt(TypedDict):
66
+ schema_version: int
67
+ complete: bool
68
+ schedule_sha256: str
69
+ tranche: int
70
+ rows: int
71
+ data_sha256: str
72
+ synthetic_sha256: str
73
+ ordered_ids_sha256: str
74
+ audit_path: str
75
+ audit_sha256: str
76
+ calibration_sha256: str
77
+ controls_audit_sha256: str
78
+ previous_receipts_sha256: list[str]
79
+
80
+
81
+ def digest(path: Path) -> str:
82
+ with path.open("rb") as stream:
83
+ return hashlib.file_digest(stream, "sha256").hexdigest()
84
+
85
+
86
+ def ids_sha256(ids: Sequence[str]) -> str:
87
+ return hashlib.sha256(json.dumps(ids, separators=(",", ":"), ensure_ascii=False).encode()).hexdigest()
88
+
89
+
90
+ def path_at(schedule_path: Path, value: str) -> Path:
91
+ path = Path(value)
92
+ return path if path.is_absolute() else schedule_path.parent / path
93
+
94
+
95
+ def relative(path: Path, schedule_path: Path) -> str:
96
+ # Keep repository symlink paths portable; do not resolve their storage targets.
97
+ return os.path.relpath(path.absolute(), schedule_path.parent.absolute())
98
+
99
+
100
+ def rows(path: Path) -> Iterator[Example]:
101
+ with path.open() as stream:
102
+ for line in stream:
103
+ yield cast(Example, json.loads(line))
104
+
105
+
106
+ def document(path: Path) -> dict[str, JSONValue]:
107
+ return cast(dict[str, JSONValue], json.loads(path.read_bytes()))
108
+
109
+
110
+ def load_schedule(path: Path) -> Schedule:
111
+ """Validate structure without requiring future dataset files to exist."""
112
+ raw = gzip.decompress(path.read_bytes()) if path.suffix == ".gz" else path.read_bytes()
113
+ schedule = cast(Schedule, json.loads(raw))
114
+ tranches = schedule["tranches"]
115
+ if schedule["schema_version"] != 1 or [item["index"] for item in tranches] != list(range(TRANCHES)):
116
+ raise ValueError(f"Schedule must contain exactly {TRANCHES} ordered tranches")
117
+ concatenated: list[str] = []
118
+ for item in tranches:
119
+ new, replay, ordered = item["synthetic_ids"], item["replay_ids"], item["ordered_ids"]
120
+ if (len(new) != NEW_ROWS or len(replay) != REPLAY_ROWS or len(ordered) != NEW_ROWS + REPLAY_ROWS
121
+ or len(set(new + replay)) != len(ordered) or set(new + replay) != set(ordered)):
122
+ raise ValueError(f"Tranche {item['index']} has invalid coverage or repeated IDs")
123
+ concatenated.extend(ordered)
124
+ if (concatenated != schedule["ordered_ids"] or len(concatenated) != schedule["total_rows"]
125
+ or len(concatenated) != TOTAL_ROWS or len(set(concatenated)) != TOTAL_ROWS):
126
+ raise ValueError("Global schedule order, count or uniqueness differs")
127
+ return schedule
128
+
129
+
130
+ def immutable(path: Path, payload: bytes) -> None:
131
+ if path.exists():
132
+ if path.read_bytes() != payload:
133
+ raise ValueError(f"Refusing to replace immutable artifact: {path}")
134
+ return
135
+ path.parent.mkdir(parents=True, exist_ok=True)
136
+ temporary = path.with_name(path.name + ".tmp")
137
+ with temporary.open("xb") as stream:
138
+ stream.write(payload)
139
+ stream.flush()
140
+ os.fsync(stream.fileno())
141
+ temporary.chmod(0o444)
142
+ temporary.replace(path)
143
+
144
+
145
+ def encoded(example: Example) -> bytes:
146
+ return (json.dumps(example, ensure_ascii=False) + "\n").encode()
147
+
148
+
149
+ def rank(value: str) -> str:
150
+ return hashlib.sha256(f"{SEED}:{value}".encode()).hexdigest()
151
+
152
+
153
+ def make_schedule(plan_path: Path, original: Path, public_calibration: Path,
154
+ controls_audit: Path, calibration: Path, output: Path) -> Schedule:
155
+ plan = load_plan(plan_path)
156
+ groups: dict[tuple[str, str], dict[str, list[Slot]]] = defaultdict(lambda: defaultdict(list))
157
+ for slot in plan["slots"]:
158
+ if slot["split"] == "train":
159
+ groups[slot["task"], slot["language"]][slot["family"]].append(slot)
160
+ allocated: list[list[Slot]] = [[] for _ in range(TRANCHES)]
161
+ for key, families in sorted(groups.items()):
162
+ if len(families) % TRANCHES or any(len(pair) != 2 for pair in families.values()):
163
+ raise ValueError(f"Cannot assign exact paired task/language quotas: {key}")
164
+ quota = len(families) // TRANCHES
165
+ pilot = sorted((family for family, pair in families.items() if pair[0]["pilot"]), key=rank)
166
+ pilot_set = set(pilot)
167
+ remaining = sorted((family for family in families if family not in pilot_set), key=rank)
168
+ if len(pilot) > quota:
169
+ raise ValueError("Pilot exceeds the first tranche quota")
170
+ cursor = 0
171
+ # Split each original 10,000-row allocation in two; keep every pilot family first.
172
+ for index in range(TRANCHES // 2):
173
+ chosen = pilot[:] if index == 0 else []
174
+ take = 2 * quota - len(chosen)
175
+ chosen += remaining[cursor:cursor + take]
176
+ cursor += take
177
+ for half in range(2):
178
+ allocated[2 * index + half].extend(slot for family in chosen[half * quota:(half + 1) * quota] for slot in families[family])
179
+
180
+ old: dict[str, dict[str, JSONValue]] = {}
181
+ by_suite: dict[str, dict[str, list[str]]] = defaultdict(lambda: defaultdict(list))
182
+ for row in rows(original):
183
+ if row["id"] in old:
184
+ raise ValueError("Original training IDs repeat")
185
+ old[row["id"]] = {"id": row["id"], "family": row["family"], "suite": row["suite"],
186
+ "kind": "replay", "label": row["label"], "image_count": len(row.get("images", []))}
187
+ by_suite[row["suite"]][row["family"]].append(row["id"])
188
+ if len(old) != 96000:
189
+ raise ValueError("Expected the frozen original 96,000 training rows")
190
+ replay: list[list[str]] = [[] for _ in range(TRANCHES)]
191
+ for suite, replay_families in sorted(by_suite.items()):
192
+ size = sum(map(len, replay_families.values()))
193
+ if size % (5 * TRANCHES):
194
+ raise ValueError(f"Source quota is not divisible across {TRANCHES} tranches: {suite}")
195
+ quota = size // 5
196
+ selected: list[str] = []
197
+ for family in sorted(replay_families, key=lambda value: rank(f"replay:{suite}:{value}")):
198
+ if len(selected) + len(replay_families[family]) <= quota:
199
+ selected.extend(sorted(replay_families[family], key=rank))
200
+ if len(selected) != quota:
201
+ raise ValueError(f"Whole-family replay sampling did not fill {suite}: {len(selected)}/{quota}")
202
+ for index in range(TRANCHES):
203
+ replay[index].extend(selected[index * (quota // TRANCHES):(index + 1) * (quota // TRANCHES)])
204
+
205
+ assignments: list[dict[str, JSONValue]] = []
206
+ tranches: list[Tranche] = []
207
+ for index, slots in enumerate(allocated):
208
+ slots.sort(key=lambda slot: (rank(f"tranche:{index}:{slot['family']}"), slot["variant"]))
209
+ task_counts = Counter(slot["task"] for slot in slots)
210
+ if dict(task_counts) != {task: count // TRANCHES for task, count in COUNTS.items()} or len(replay[index]) != REPLAY_ROWS:
211
+ raise ValueError("Tranche task or replay quotas differ")
212
+ for task, count in task_counts.items():
213
+ if Counter(slot["language"] for slot in slots if slot["task"] == task) != {"EN": count * 7 // 10, "DE": count // 10, "ES": count // 10, "FR": count // 10}:
214
+ raise ValueError("Tranche task/language margins differ")
215
+ new_ids = [slot["id"] for slot in slots]
216
+ ordered = new_ids + replay[index]
217
+ random.Random(int(rank(f"training-order:{index}"), 16)).shuffle(ordered)
218
+ folder = Path("data/continuation/tranches") / f"{index:03d}"
219
+ tranches.append({"index": index, "synthetic_ids": new_ids,
220
+ "synthetic_families": list(dict.fromkeys(slot["family"] for slot in slots)),
221
+ "replay_ids": replay[index], "ordered_ids": ordered,
222
+ "data_path": relative(folder / "train.jsonl", output),
223
+ "synthetic_path": relative(folder / "synthetic.jsonl", output),
224
+ "manifest_path": relative(Path("progress/continuation/tranches") / f"{index:03d}.json", output),
225
+ "counts": {"synthetic_tasks": dict(task_counts),
226
+ "replay_suites": dict(Counter(str(old[id_]["suite"]) for id_ in replay[index])),
227
+ "replay_labels": dict(Counter(f"{old[id_]['suite']}:{old[id_]['label']}" for id_ in replay[index])),
228
+ "replay_image_rows": sum(bool(old[id_]["image_count"]) for id_ in replay[index])}})
229
+ for slot in slots:
230
+ assignments.append({"id": slot["id"], "family": slot["family"], "kind": "synthetic", "tranche": index,
231
+ "task": slot["task"], "language": slot["language"], "domain": slot["domain"],
232
+ "phenomenon": slot["phenomenon"], "style": slot["style"], "length": slot["length"],
233
+ "ontology": slot["ontology"], "pilot": slot["pilot"], "menu_size": len(slot["option_labels"]),
234
+ "label": slot["desired_label"]})
235
+ assignments.extend({**old[id_], "tranche": index} for id_ in replay[index])
236
+ global_ids = [id_ for tranche in tranches for id_ in tranche["ordered_ids"]]
237
+ if len(global_ids) != TOTAL_ROWS or len(set(global_ids)) != TOTAL_ROWS:
238
+ raise ValueError("Schedule IDs repeat or total differs")
239
+ if sum(slot["pilot"] for slot in allocated[0]) != 2000 or any(slot["pilot"] for group in allocated[1:] for slot in group):
240
+ raise ValueError("All 2,000 pilot examples must be in tranche zero")
241
+ public_ids = [row["id"] for row in rows(public_calibration)]
242
+ controls = {split: [slot["id"] for slot in plan["slots"] if slot["split"] == split]
243
+ for split in ("calibration", "confirmation")}
244
+ if len(set(public_ids)) != 2500 or len(public_ids) != 2500 or any(len(ids) != 1000 for ids in controls.values()):
245
+ raise ValueError("Expected 2,500 public calibration and 1,000 examples per synthetic control")
246
+ replay_ids = [id_ for group in replay for id_ in group]
247
+ selected_set = set(replay_ids)
248
+ replay_hash = hashlib.sha256()
249
+ for row in rows(original):
250
+ if row["id"] in selected_set:
251
+ replay_hash.update(encoded(row))
252
+ return {"schema_version": 1, "seed": SEED, "total_rows": TOTAL_ROWS, "ordered_ids": global_ids,
253
+ "plan_path": relative(plan_path, output), "plan_sha256": digest(plan_path),
254
+ "original_train_path": relative(original, output), "original_train_sha256": digest(original),
255
+ "replay_ids": replay_ids, "selected_replay_sha256": replay_hash.hexdigest(),
256
+ "selected_replay_ids_sha256": ids_sha256(replay_ids),
257
+ "public_calibration_path": relative(public_calibration, output), "public_calibration_sha256": digest(public_calibration),
258
+ "calibration_path": relative(calibration, output), "calibration_ids": public_ids + controls["calibration"],
259
+ "controls_audit_path": relative(controls_audit, output),
260
+ "synthetic_calibration_path": relative(controls_audit.parent / "calibration.jsonl", output),
261
+ "synthetic_confirmation_path": relative(controls_audit.parent / "confirmation.jsonl", output),
262
+ "control_ids": controls, "audit_order": controls["calibration"] + controls["confirmation"] + [id_ for tranche in tranches for id_ in tranche["synthetic_ids"]],
263
+ "assignments": assignments, "code_sha256": {"src/autojev/sft_pipeline.py": digest(Path(__file__))}, "tranches": tranches}
264
+
265
+
266
+ def verify_output(report: dict[str, JSONValue], split: str, path: Path, expected: list[str]) -> list[Example]:
267
+ outputs = cast(dict[str, dict[str, JSONValue]], report["outputs"])
268
+ result = list(rows(path))
269
+ if outputs[split]["sha256"] != digest(path) or len(result) != len(expected) or {row["id"] for row in result} != set(expected):
270
+ raise ValueError(f"Audited {split} data differs from its receipt or scheduled IDs")
271
+ return result
272
+
273
+
274
+ def freeze_tranche(schedule_path: Path, index: int, synthetic: Path, audit_path: Path) -> Receipt:
275
+ schedule = load_schedule(schedule_path)
276
+ schedule_hash = digest(schedule_path)
277
+ if not 0 <= index < TRANCHES:
278
+ raise ValueError(f"Tranche index must be 0..{TRANCHES - 1}")
279
+ if schedule["code_sha256"]["src/autojev/sft_pipeline.py"] != digest(Path(__file__)):
280
+ raise ValueError("Packaging code changed after the schedule freeze")
281
+ if digest(path_at(schedule_path, schedule["plan_path"])) != schedule["plan_sha256"]:
282
+ raise ValueError("Generation plan changed after the schedule freeze")
283
+ original = path_at(schedule_path, schedule["original_train_path"])
284
+ if digest(original) != schedule["original_train_sha256"]:
285
+ raise ValueError("Original training dataset changed")
286
+ controls_path = path_at(schedule_path, schedule["controls_audit_path"])
287
+ controls = document(controls_path)
288
+ if (controls.get("complete") is not True or controls.get("phase") != "controls" or controls.get("accepted_rows") != 2000
289
+ or cast(dict[str, JSONValue], controls["plan"])["sha256"] != schedule["plan_sha256"]):
290
+ raise ValueError("Complete frozen controls audit is required before any training tranche")
291
+ control_paths = {"calibration": schedule["synthetic_calibration_path"], "confirmation": schedule["synthetic_confirmation_path"]}
292
+ control_rows = {split: verify_output(controls, split, path_at(schedule_path, value), schedule["control_ids"][split])
293
+ for split, value in control_paths.items()}
294
+ public = path_at(schedule_path, schedule["public_calibration_path"])
295
+ if digest(public) != schedule["public_calibration_sha256"]:
296
+ raise ValueError("Public calibration data changed")
297
+ temperature = {row["id"]: row for row in [*rows(public), *control_rows["calibration"]]}
298
+ if len(temperature) != 3500 or set(temperature) != set(schedule["calibration_ids"]):
299
+ raise ValueError("Combined calibration data must contain the exact 3,500 scheduled IDs")
300
+ temperature_path = path_at(schedule_path, schedule["calibration_path"])
301
+ immutable(temperature_path, b"".join(encoded(temperature[id_]) for id_ in schedule["calibration_ids"]))
302
+ temperature_hash = digest(temperature_path)
303
+ previous: list[str] = []
304
+ prior_hashes = {digest(path_at(schedule_path, value)) for value in control_paths.values()}
305
+ for prior in schedule["tranches"][:index]:
306
+ path = path_at(schedule_path, prior["manifest_path"])
307
+ receipt = cast(Receipt, document(path))
308
+ if (receipt["complete"] is not True or receipt["schedule_sha256"] != schedule_hash or receipt["tranche"] != prior["index"]
309
+ or receipt["previous_receipts_sha256"] != previous or receipt["calibration_sha256"] != temperature_hash
310
+ or receipt["controls_audit_sha256"] != digest(controls_path)
311
+ or receipt["data_sha256"] != digest(path_at(schedule_path, prior["data_path"]))
312
+ or receipt["synthetic_sha256"] != digest(path_at(schedule_path, prior["synthetic_path"]))
313
+ or receipt["audit_sha256"] != digest(path_at(schedule_path, receipt["audit_path"]))):
314
+ raise ValueError("An earlier immutable tranche receipt or its inputs changed")
315
+ previous.append(digest(path))
316
+ prior_hashes.add(receipt["synthetic_sha256"])
317
+ audit = document(audit_path)
318
+ actual_prior = {str(item["sha256"]) for item in cast(list[dict[str, JSONValue]], audit["prior_inputs"])}
319
+ if (audit.get("complete") is not True or audit.get("phase") != "tranche" or audit.get("accepted_rows") != NEW_ROWS
320
+ or audit.get("schedule_sha256") != schedule_hash or audit.get("tranche") != index
321
+ or cast(dict[str, JSONValue], audit["plan"])["sha256"] != schedule["plan_sha256"] or not prior_hashes <= actual_prior):
322
+ raise ValueError("Tranche audit is incomplete or does not bind the schedule and all previous accepted data")
323
+ tranche = schedule["tranches"][index]
324
+ new_rows = verify_output(audit, "train", synthetic, tranche["synthetic_ids"])
325
+ replay_set = set(tranche["replay_ids"])
326
+ selected = {row["id"]: row for row in rows(original) if row["id"] in replay_set}
327
+ selected.update({row["id"]: row for row in new_rows})
328
+ if set(selected) != set(tranche["ordered_ids"]):
329
+ raise ValueError("Packaged training IDs differ from the schedule")
330
+ new_path, data_path = path_at(schedule_path, tranche["synthetic_path"]), path_at(schedule_path, tranche["data_path"])
331
+ immutable(new_path, synthetic.read_bytes())
332
+ immutable(data_path, b"".join(encoded(selected[id_]) for id_ in tranche["ordered_ids"]))
333
+ result: Receipt = {"schema_version": 1, "complete": True, "schedule_sha256": schedule_hash, "tranche": index,
334
+ "rows": len(selected), "data_sha256": digest(data_path), "synthetic_sha256": digest(new_path),
335
+ "ordered_ids_sha256": ids_sha256(tranche["ordered_ids"]), "audit_path": relative(audit_path, schedule_path),
336
+ "audit_sha256": digest(audit_path), "calibration_sha256": temperature_hash,
337
+ "controls_audit_sha256": digest(controls_path), "previous_receipts_sha256": previous}
338
+ receipt_path = path_at(schedule_path, tranche["manifest_path"])
339
+ exists = receipt_path.exists()
340
+ immutable(receipt_path, (json.dumps(result, indent=2, ensure_ascii=False) + "\n").encode())
341
+ if not exists:
342
+ record("training_tranche_ready", tranche=index, rows=len(selected), schedule_sha256=schedule_hash,
343
+ receipt=str(receipt_path), receipt_sha256=digest(receipt_path), data_sha256=result["data_sha256"])
344
+ return result
345
+
346
+
347
+ def quick_key(identifier: str) -> str:
348
+ return rank(f"quick-evaluation:{identifier}")
349
+
350
+
351
+ def quick_public(path: Path, per_suite: int) -> list[Example]:
352
+ groups: dict[str, list[Example]] = defaultdict(list)
353
+ for row in rows(path):
354
+ groups[row["suite"]].append(row)
355
+ if len(groups) != 5:
356
+ raise ValueError("Quick public panels require five source suites")
357
+ selected = [row for _, group in sorted(groups.items())
358
+ for row in sorted(group, key=lambda row: quick_key(row["id"]))[:per_suite]]
359
+ if len(selected) != 5 * per_suite or len({row["family"] for row in selected}) != len(selected):
360
+ raise ValueError("Expected enough distinct public source families")
361
+ return selected
362
+
363
+
364
+ def make_quick_plan(output: Path, generation_plan: Path) -> dict[str, JSONValue]:
365
+ """Choose diagnostic IDs from construction metadata, never model outcomes."""
366
+ if output.exists():
367
+ raise ValueError("Quick evaluation IDs are already frozen")
368
+ paths = {"public": Path("data/transfer/public.jsonl"), "guard": Path("data/transfer/synthetic.jsonl"),
369
+ "public_temperature": Path("data/continuation/public-temperature.jsonl")}
370
+ public = quick_public(paths["public"], 200)
371
+ public_temperature = quick_public(paths["public_temperature"], 100)
372
+ cells: dict[tuple[str, str, str], list[Example]] = defaultdict(list)
373
+ for row in rows(paths["guard"]):
374
+ source = row["source"]
375
+ cells[str(source["task"]), str(source["condition"]), str(source["language"])].append(row)
376
+ if len(cells) != 200:
377
+ raise ValueError("Guard needs ten tasks × five conditions × four languages")
378
+ guard: list[Example] = []
379
+ tasks = sorted({cell[0] for cell in cells})
380
+ for task in tasks:
381
+ conditions = sorted({cell[1] for cell in cells if cell[0] == task}, key=lambda value: quick_key(f"{task}:{value}"))
382
+ other_languages = sorted(("DE", "ES", "FR"), key=lambda value: quick_key(f"{task}:{value}"))
383
+ for index, condition in enumerate(conditions):
384
+ quotas = {"EN": 2 if index < 3 else 1, **{language: int(index >= 3 or language != other_languages[index]) for language in other_languages}}
385
+ for language, count in sorted(quotas.items()):
386
+ guard_chosen = sorted(cells[task, condition, language], key=lambda row: quick_key(row["id"]))[:count]
387
+ if len(guard_chosen) != count:
388
+ raise ValueError("Insufficient guard rows for the fixed task/condition/language quotas")
389
+ guard.extend(guard_chosen)
390
+ if len({row["family"] for row in guard}) != 200:
391
+ raise ValueError("Quick guard families must be distinct")
392
+
393
+ plan = load_plan(generation_plan)
394
+ by_cell: dict[tuple[str, str], dict[str, list[Slot]]] = defaultdict(lambda: defaultdict(list))
395
+ for slot in plan["slots"]:
396
+ if slot["split"] == "calibration":
397
+ by_cell[slot["task"], slot["language"]][slot["family"]].append(slot)
398
+ family_targets = {task: count // 500 for task, count in COUNTS.items()}
399
+ weights = {"EN": 7, "DE": 1, "ES": 1, "FR": 1}
400
+ # Paired families make some task/language ideals fractional. Preserve both
401
+ # integer margins, with at least one family in every task/language cell.
402
+ allocation = {(task, language): max(1, total * weight // 10)
403
+ for task, total in family_targets.items() for language, weight in weights.items()}
404
+ remaining_task = {task: total - sum(allocation[task, language] for language in weights)
405
+ for task, total in family_targets.items()}
406
+ remaining_language = {language: weight * 10 - sum(allocation[task, language] for task in family_targets)
407
+ for language, weight in weights.items()}
408
+ while sum(remaining_task.values()):
409
+ choices = [(task, language) for task in family_targets for language in weights
410
+ if remaining_task[task] > 0 and remaining_language[language] > 0]
411
+ if not choices:
412
+ raise ValueError("Cannot fill declared paired temperature margins")
413
+ task, language = min(choices, key=lambda cell: (
414
+ -(family_targets[cell[0]] * weights[cell[1]] - 10 * allocation[cell]), quick_key(":".join(cell))))
415
+ allocation[task, language] += 1
416
+ remaining_task[task] -= 1
417
+ remaining_language[language] -= 1
418
+ synthetic_ids: list[str] = []
419
+ for cell, count in sorted(allocation.items()):
420
+ families = by_cell[cell]
421
+ chosen = sorted(families, key=quick_key)[:count]
422
+ if len(chosen) != count or any(len(families[family]) != 2 for family in chosen):
423
+ raise ValueError("Insufficient complete calibration families")
424
+ synthetic_ids.extend(slot["id"] for family in chosen
425
+ for slot in sorted(families[family], key=lambda slot: slot["variant"]))
426
+ if len(synthetic_ids) != 200 or any(remaining_language.values()):
427
+ raise ValueError("Synthetic temperature row or language totals differ")
428
+ destination = Path("data/continuation")
429
+ public_path, guard_path = destination / "quick-public.jsonl", destination / "quick-guard.jsonl"
430
+ immutable(public_path, b"".join(map(encoded, public)))
431
+ immutable(guard_path, b"".join(map(encoded, guard)))
432
+ result: dict[str, JSONValue] = {
433
+ "schema_version": 1, "seed": SEED, "selection_namespace": "quick-evaluation",
434
+ "purpose": "Diagnostic only; selection uses the complete public and guard cohorts every50 updates and at the final update.",
435
+ "quick_every": 20, "full_every": 50, "generation_plan": {"path": str(generation_plan), "sha256": digest(generation_plan)},
436
+ "source_files": {key: {"path": str(path), "sha256": digest(path)} for key, path in paths.items()},
437
+ "code_sha256": digest(Path(__file__)),
438
+ "public": {"path": str(public_path), "sha256": digest(public_path), "rows": 1000,
439
+ "ids": [row["id"] for row in public], "rule": "200 SHA-ranked IDs per source; distinct source families."},
440
+ "guard": {"path": str(guard_path), "sha256": digest(guard_path), "rows": 200,
441
+ "ids": [row["id"] for row in guard], "rule": "Four SHA-ranked IDs per task/condition; three SHA-ranked conditions use twoEN and omit a distinct other language, remaining two use one per language.",
442
+ "language_weighting": "Preserve the full guard40/20/20/20 mixture: eightEN and fourDE/ES/FR per task.",
443
+ "task_condition_language_rows": {task: {condition: {language: sum(row["source"]["task"] == task and row["source"]["condition"] == condition and row["source"]["language"] == language for row in guard) for language in ("EN", "DE", "ES", "FR")} for condition in sorted({cell[1] for cell in cells if cell[0] == task})} for task in tasks}},
444
+ "temperature": {"path": str(destination / "quick-temperature.jsonl"), "rows": 700,
445
+ "public_ids": [row["id"] for row in public_temperature], "synthetic_ids": cast(JSONValue, synthetic_ids),
446
+ "ids": cast(JSONValue, [row["id"] for row in public_temperature] + synthetic_ids),
447
+ "synthetic_task_language_rows": {task: {language: 2 * allocation[task, language] for language in weights} for task in family_targets},
448
+ "rule": "100 SHA-ranked public IDs per source plus100 whole synthetic families. Exact task and aggregate70/10/10/10 language margins; floor/minimum-one families then largest deficit with SHA tie-breaking.",
449
+ "materialization": "After the complete synthetic controls audit; no synthetic text or fitted temperature is chosen here."},
450
+ "limitations": ["Small-panel sampling variation is diagnostic, not a checkpoint-selection signal.",
451
+ "All labels/states/options are copied unchanged from their full parent folds; no model scores or confirmation records enter selection.",
452
+ "Synthetic task/language cells use whole-family integer rounding; the exact table above replaces infeasible fractional per-cell quotas."],
453
+ }
454
+ immutable(output, (json.dumps(result, indent=2, ensure_ascii=False) + "\n").encode())
455
+ record("quick_evaluation_ids_frozen", path=str(output), sha256=digest(output), public_rows=1000, guard_rows=200, temperature_rows=700)
456
+ return result
457
+
458
+
459
+ def materialize_quick_temperature(plan_path: Path, controls_audit: Path) -> Path:
460
+ plan = document(plan_path)
461
+ sources = cast(dict[str, dict[str, JSONValue]], plan["source_files"])
462
+ public_source = sources["public_temperature"]
463
+ public = Path(str(public_source["path"]))
464
+ if digest(public) != public_source["sha256"]:
465
+ raise ValueError("Public temperature source changed after quick-ID selection")
466
+ controls = document(controls_audit)
467
+ generation_plan = cast(dict[str, JSONValue], plan["generation_plan"])
468
+ if (controls.get("complete") is not True or controls.get("phase") != "controls" or controls.get("accepted_rows") != 2000
469
+ or cast(dict[str, JSONValue], controls["plan"])["sha256"] != generation_plan["sha256"]):
470
+ raise ValueError("Complete matching controls audit is required")
471
+ synthetic = controls_audit.parent / "calibration.jsonl"
472
+ outputs = cast(dict[str, dict[str, JSONValue]], controls["outputs"])
473
+ if digest(synthetic) != outputs["calibration"]["sha256"]:
474
+ raise ValueError("Synthetic calibration differs from its audit")
475
+ temperature = cast(dict[str, JSONValue], plan["temperature"])
476
+ ids = cast(list[str], temperature["ids"])
477
+ full_rows = [*rows(public), *rows(synthetic)]
478
+ source = {row["id"]: row for row in full_rows}
479
+ if len(full_rows) != 3500 or len(source) != 3500 or len(ids) != 700 or not set(ids) <= source.keys():
480
+ raise ValueError("Quick temperature IDs do not match the complete parent fold")
481
+ destination = Path(str(temperature["path"]))
482
+ existed = destination.exists()
483
+ immutable(destination, b"".join(encoded(source[id_]) for id_ in ids))
484
+ receipt = {"quick_plan_sha256": digest(plan_path), "controls_audit_sha256": digest(controls_audit),
485
+ "path": str(destination), "sha256": digest(destination), "rows": 700}
486
+ immutable(plan_path.with_name("quick-temperature-receipt.json"), (json.dumps(receipt, indent=2) + "\n").encode())
487
+ if not existed:
488
+ record("quick_evaluation_temperature_ready", **receipt)
489
+ return destination
490
+
491
+
492
+ def main() -> None:
493
+ parser = argparse.ArgumentParser(description=__doc__)
494
+ parser.add_argument("command", choices=("plan", "freeze", "quick-plan", "quick-temperature"))
495
+ parser.add_argument("--schedule", type=Path)
496
+ parser.add_argument("--quick-plan", type=Path, default=Path("progress/continuation/quick-eval-plan.json"))
497
+ parser.add_argument("--generation-plan", type=Path, default=Path("data/continuation/generation-plan-v2.json.gz"))
498
+ parser.add_argument("--original-train", type=Path, default=Path("data/train.jsonl"))
499
+ parser.add_argument("--public-calibration", type=Path, default=Path("data/continuation/public-temperature.jsonl"))
500
+ parser.add_argument("--controls-audit", type=Path, default=Path("data/continuation/controls-audit/audit.json"))
501
+ parser.add_argument("--calibration", type=Path, default=Path("data/continuation/temperature.jsonl"))
502
+ parser.add_argument("--tranche", type=int)
503
+ parser.add_argument("--synthetic", type=Path)
504
+ parser.add_argument("--audit", type=Path)
505
+ args = parser.parse_args()
506
+ if not os.getenv("AUTOJEV_EVENTS"):
507
+ parser.error("Set AUTOJEV_EVENTS explicitly to the continuation event log")
508
+ if args.command == "quick-plan":
509
+ make_quick_plan(args.quick_plan, args.generation_plan)
510
+ print(json.dumps({"path": str(args.quick_plan), "sha256": digest(args.quick_plan)}))
511
+ elif args.command == "quick-temperature":
512
+ print(materialize_quick_temperature(args.quick_plan, args.controls_audit))
513
+ elif args.command == "plan":
514
+ if args.schedule is None:
515
+ parser.error("plan requires --schedule")
516
+ if args.schedule.exists():
517
+ parser.error("Schedule already exists; refusing to replace it")
518
+ schedule = make_schedule(args.generation_plan, args.original_train, args.public_calibration, args.controls_audit, args.calibration, args.schedule)
519
+ payload = json.dumps(schedule, ensure_ascii=False, separators=(",", ":")).encode()
520
+ immutable(args.schedule, gzip.compress(payload, mtime=0) if args.schedule.suffix == ".gz" else payload)
521
+ load_schedule(args.schedule)
522
+ record("training_schedule_frozen", path=str(args.schedule), sha256=digest(args.schedule), rows=TOTAL_ROWS,
523
+ synthetic_rows=50000, replay_rows=19200, tranches=TRANCHES, global_batch=256, updates=271)
524
+ print(json.dumps({"path": str(args.schedule), "sha256": digest(args.schedule), "rows": TOTAL_ROWS, "updates": 271}))
525
+ else:
526
+ if args.schedule is None or args.tranche is None or args.synthetic is None or args.audit is None:
527
+ parser.error("freeze requires --schedule, --tranche, --synthetic and --audit")
528
+ print(json.dumps(freeze_tranche(args.schedule, args.tranche, args.synthetic, args.audit), indent=2))
529
+
530
+
531
+ if __name__ == "__main__":
532
+ main()