deeploop 0.1.0__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.
- deeploop/__init__.py +1 -0
- deeploop/artifacts/__init__.py +2 -0
- deeploop/artifacts/artifact_packager.py +1046 -0
- deeploop/artifacts/mission_package.py +121 -0
- deeploop/artifacts/release_automation.py +444 -0
- deeploop/autonomy/__init__.py +2 -0
- deeploop/autonomy/gate_taxonomy.py +312 -0
- deeploop/autonomy/mission_autonomy.py +238 -0
- deeploop/autonomy/mission_contract_snapshot.py +146 -0
- deeploop/autonomy/operating_modes.py +53 -0
- deeploop/autonomy/operator_inbox.py +198 -0
- deeploop/cli/__init__.py +1 -0
- deeploop/cli/analyze.py +235 -0
- deeploop/cli/init_mission.py +49 -0
- deeploop/cli/package_mission.py +34 -0
- deeploop/cli/run_project.py +59 -0
- deeploop/core/__init__.py +2 -0
- deeploop/core/config_paths.py +22 -0
- deeploop/core/dotted.py +16 -0
- deeploop/core/ledger.py +39 -0
- deeploop/core/paths.py +66 -0
- deeploop/core/structured_io.py +96 -0
- deeploop/fresh_context_redteam.py +382 -0
- deeploop/mission/__init__.py +2 -0
- deeploop/mission/_autonomy_gap_telemetry.py +287 -0
- deeploop/mission/_constants.py +19 -0
- deeploop/mission/_monitor_classification.py +1071 -0
- deeploop/mission/_monitor_render.py +574 -0
- deeploop/mission/_monitor_snapshot.py +266 -0
- deeploop/mission/_operator_surface.py +475 -0
- deeploop/mission/_runtime_contract.py +76 -0
- deeploop/mission/_runtime_persistence.py +360 -0
- deeploop/mission/mission_decision_engine.py +1582 -0
- deeploop/mission/mission_management.py +1468 -0
- deeploop/mission/mission_memory.py +732 -0
- deeploop/mission/mission_monitor.py +6 -0
- deeploop/mission/mission_progress.py +9 -0
- deeploop/mission/mission_runtime.py +2028 -0
- deeploop/mission/mission_scheduler.py +880 -0
- deeploop/mission/mission_state.py +47 -0
- deeploop/mission/orchestrator.py +472 -0
- deeploop/mission/plain_folder_followup.py +227 -0
- deeploop/mission/project_bootstrap.py +173 -0
- deeploop/mission/project_runner.py +208 -0
- deeploop/platform/__init__.py +13 -0
- deeploop/platform/contracts.py +549 -0
- deeploop/project_contract.py +256 -0
- deeploop/research/__init__.py +2 -0
- deeploop/research/confound_guard.py +1005 -0
- deeploop/research/indexed_memory.py +553 -0
- deeploop/research/novelty_refresh.py +529 -0
- deeploop/research/sanity_gates.py +694 -0
- deeploop/research/self_correction.py +624 -0
- deeploop/research/self_optimization.py +480 -0
- deeploop/research/statistical_rigor.py +728 -0
- deeploop/research/utility_scorer.py +852 -0
- deeploop/runtime/__init__.py +2 -0
- deeploop/runtime/_prompt_renderer.py +306 -0
- deeploop/runtime/_stage_kernel_registry.py +43 -0
- deeploop/runtime/_stage_kernel_reporting.py +224 -0
- deeploop/runtime/_stage_kernel_resolution.py +99 -0
- deeploop/runtime/adaptation_training_runtime.py +646 -0
- deeploop/runtime/copilot_adapter.py +30 -0
- deeploop/runtime/metric_ratchets.py +169 -0
- deeploop/runtime/mission_executor_registry.py +424 -0
- deeploop/runtime/plain_folder_adapter.py +125 -0
- deeploop/runtime/provider_launcher.py +800 -0
- deeploop/runtime/recursive_agent_runtime.py +1320 -0
- deeploop/runtime/runtime_recovery.py +289 -0
- deeploop/runtime/sandbox.py +51 -0
- deeploop/runtime/self_healing_runtime.py +871 -0
- deeploop/runtime/stage_kernels.py +3715 -0
- deeploop/testing/__init__.py +19 -0
- deeploop/testing/acceptance_campaigns.py +198 -0
- deeploop/testing/plain_folder_proof_matrix.py +132 -0
- deeploop/testing/proof_matrix_reviews.py +305 -0
- deeploop/testing/test_tiers.py +114 -0
- deeploop-0.1.0.dist-info/METADATA +210 -0
- deeploop-0.1.0.dist-info/RECORD +83 -0
- deeploop-0.1.0.dist-info/WHEEL +5 -0
- deeploop-0.1.0.dist-info/entry_points.txt +6 -0
- deeploop-0.1.0.dist-info/licenses/LICENSE +21 -0
- deeploop-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from deeploop.artifacts.release_automation import (
|
|
11
|
+
build_package_release_automation,
|
|
12
|
+
build_release_candidate_review,
|
|
13
|
+
load_release_candidate_policy,
|
|
14
|
+
materialize_release_candidate_review,
|
|
15
|
+
)
|
|
16
|
+
from deeploop.core.structured_io import (
|
|
17
|
+
load_json_object as _load_json,
|
|
18
|
+
load_jsonl_objects as _load_jsonl,
|
|
19
|
+
load_yaml_mapping as _load_yaml,
|
|
20
|
+
)
|
|
21
|
+
from deeploop.core.paths import REPO_ROOT, RUNS_DIR, WORKSPACE_ROOT
|
|
22
|
+
|
|
23
|
+
PACKAGE_CONTRACT_PATH = REPO_ROOT / "configs" / "runtime" / "artifact-package-contract.yaml"
|
|
24
|
+
PACKAGE_SCHEMA_PATH = REPO_ROOT / "schemas" / "mission-artifact-package.schema.json"
|
|
25
|
+
EVIDENCE_POLICY_PATH = REPO_ROOT / "configs" / "autonomy" / "evidence-policy.yaml"
|
|
26
|
+
|
|
27
|
+
CLAIM_ORDER = {
|
|
28
|
+
"not-ready": -1,
|
|
29
|
+
"exploratory": 0,
|
|
30
|
+
"replicated": 1,
|
|
31
|
+
"paper-candidate": 2,
|
|
32
|
+
"release-candidate": 3,
|
|
33
|
+
}
|
|
34
|
+
DEFAULT_CATEGORIES = (
|
|
35
|
+
"mission_specs",
|
|
36
|
+
"mission_configs",
|
|
37
|
+
"ledgers",
|
|
38
|
+
"findings",
|
|
39
|
+
"manifests",
|
|
40
|
+
"kernel_outputs",
|
|
41
|
+
"critique_reports",
|
|
42
|
+
"runtime_metadata",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _remove_tree(path: Path) -> None:
|
|
47
|
+
for attempt in range(3):
|
|
48
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
49
|
+
if not path.exists():
|
|
50
|
+
return
|
|
51
|
+
if attempt < 2:
|
|
52
|
+
time.sleep(0.1)
|
|
53
|
+
if path.exists():
|
|
54
|
+
raise OSError(f"Unable to remove existing package directory: {path}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _is_relative_to(path: Path, parent: Path) -> bool:
|
|
58
|
+
try:
|
|
59
|
+
path.resolve().relative_to(parent.resolve())
|
|
60
|
+
return True
|
|
61
|
+
except ValueError:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _safe_resolve(path: str | Path) -> Path:
|
|
66
|
+
return Path(path).expanduser().resolve()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _infer_repo_root_from_contract_path(contract_path: Path) -> Path | None:
|
|
70
|
+
for parent in contract_path.parents:
|
|
71
|
+
if parent.name == "configs":
|
|
72
|
+
return parent.parent
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _resolve_contract_declared_path(raw_path: str | Path, *, contract_path: Path) -> Path:
|
|
77
|
+
raw_text = str(raw_path)
|
|
78
|
+
normalized_text = raw_text.replace("\\", "/")
|
|
79
|
+
candidate = Path(normalized_text).expanduser()
|
|
80
|
+
if candidate.is_absolute() or raw_text.startswith("~"):
|
|
81
|
+
return candidate.resolve()
|
|
82
|
+
if normalized_text.startswith(("./", "../")):
|
|
83
|
+
return (contract_path.parent / candidate).resolve()
|
|
84
|
+
repo_root = _infer_repo_root_from_contract_path(contract_path)
|
|
85
|
+
if repo_root is not None:
|
|
86
|
+
return (repo_root / candidate).resolve()
|
|
87
|
+
return (contract_path.parent / candidate).resolve()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _packaged_artifacts_root(source_path: Path) -> Path | None:
|
|
91
|
+
for parent in source_path.resolve().parents:
|
|
92
|
+
if parent.name == "artifacts":
|
|
93
|
+
return parent
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _resolve_existing_or_packaged_path(raw_path: str | Path, *, source_path: Path) -> Path:
|
|
98
|
+
resolved = _safe_resolve(raw_path)
|
|
99
|
+
if resolved.exists():
|
|
100
|
+
return resolved
|
|
101
|
+
packaged_root = _packaged_artifacts_root(source_path)
|
|
102
|
+
if packaged_root is None or not _is_relative_to(resolved, WORKSPACE_ROOT):
|
|
103
|
+
return resolved
|
|
104
|
+
candidate = packaged_root / resolved.relative_to(WORKSPACE_ROOT)
|
|
105
|
+
return candidate.resolve() if candidate.exists() else resolved
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _artifact_id_for_path(path: Path) -> str:
|
|
109
|
+
resolved = path.resolve()
|
|
110
|
+
if _is_relative_to(resolved, WORKSPACE_ROOT):
|
|
111
|
+
relative = resolved.relative_to(WORKSPACE_ROOT).as_posix()
|
|
112
|
+
else:
|
|
113
|
+
relative = resolved.as_posix().lstrip("/")
|
|
114
|
+
return relative.replace("/", "::")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _package_relative_path(path: Path, copied_root_name: str) -> Path:
|
|
118
|
+
resolved = path.resolve()
|
|
119
|
+
if _is_relative_to(resolved, WORKSPACE_ROOT):
|
|
120
|
+
return Path(copied_root_name) / resolved.relative_to(WORKSPACE_ROOT)
|
|
121
|
+
return Path(copied_root_name) / "external" / resolved.as_posix().lstrip("/")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _infer_manifest_stage(manifest_path: Path, manifest: dict[str, Any]) -> str:
|
|
125
|
+
stage = manifest.get("stage", {})
|
|
126
|
+
if isinstance(stage, dict):
|
|
127
|
+
stage_id = stage.get("id")
|
|
128
|
+
if isinstance(stage_id, str) and stage_id:
|
|
129
|
+
return stage_id
|
|
130
|
+
loop_id = str(manifest.get("loop_id", "")).lower()
|
|
131
|
+
metrics = manifest.get("metrics", {}) if isinstance(manifest.get("metrics"), dict) else {}
|
|
132
|
+
if manifest_path.name == "run_manifest.json":
|
|
133
|
+
return "baseline-evaluation"
|
|
134
|
+
if "localization_source_exists" in metrics or "intervention" in loop_id:
|
|
135
|
+
return "causal-intervention"
|
|
136
|
+
if "source_accuracy" in metrics or "candidate_count" in metrics or "mech" in loop_id:
|
|
137
|
+
return "mechanistic-localization"
|
|
138
|
+
return "unknown"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _primary_metric(manifest: dict[str, Any]) -> tuple[str | None, float | None]:
|
|
142
|
+
metrics = manifest.get("metrics", {})
|
|
143
|
+
if not isinstance(metrics, dict):
|
|
144
|
+
return (None, None)
|
|
145
|
+
for key in ("accuracy", "accuracy_post", "accuracy_delta", "source_accuracy", "baseline_accuracy"):
|
|
146
|
+
value = metrics.get(key)
|
|
147
|
+
if isinstance(value, (int, float)):
|
|
148
|
+
return (key, float(value))
|
|
149
|
+
return (None, None)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _manifest_summary_lines(manifest: dict[str, Any]) -> list[str]:
|
|
153
|
+
notes = manifest.get("notes", [])
|
|
154
|
+
lines = [str(note).strip() for note in notes if str(note).strip()]
|
|
155
|
+
metric_name, metric_value = _primary_metric(manifest)
|
|
156
|
+
if metric_name is not None and metric_value is not None:
|
|
157
|
+
lines.insert(0, f"{metric_name}: {metric_value:.6g}")
|
|
158
|
+
status = manifest.get("run", {}).get("status")
|
|
159
|
+
if isinstance(status, str) and status:
|
|
160
|
+
lines.insert(0, f"status: {status}")
|
|
161
|
+
return lines[:4]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _next_claim_state(current_state: str) -> str | None:
|
|
165
|
+
ordered = sorted(
|
|
166
|
+
((rank, state) for state, rank in CLAIM_ORDER.items() if rank >= 0),
|
|
167
|
+
key=lambda item: item[0],
|
|
168
|
+
)
|
|
169
|
+
current_rank = CLAIM_ORDER.get(current_state, 0)
|
|
170
|
+
for rank, state in ordered:
|
|
171
|
+
if rank > current_rank:
|
|
172
|
+
return state
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _build_replication_evidence(run_bundles: list[dict[str, Any]]) -> dict[str, Any]:
|
|
177
|
+
manifest_groups: dict[str, list[str]] = {}
|
|
178
|
+
for bundle in run_bundles:
|
|
179
|
+
loop_id = str(bundle.get("loop_id") or "unknown")
|
|
180
|
+
manifest_id = str(bundle.get("manifest_artifact_id") or "").strip()
|
|
181
|
+
if not manifest_id:
|
|
182
|
+
continue
|
|
183
|
+
manifest_groups.setdefault(loop_id, []).append(manifest_id)
|
|
184
|
+
return {
|
|
185
|
+
"total_manifests": len([bundle for bundle in run_bundles if bundle.get("manifest_artifact_id")]),
|
|
186
|
+
"independent_runs": len(manifest_groups),
|
|
187
|
+
"manifest_groups": manifest_groups,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _bundle_has_replication_signal(bundle: dict[str, Any]) -> bool:
|
|
192
|
+
haystack = " ".join(
|
|
193
|
+
[
|
|
194
|
+
str(bundle.get("loop_id") or ""),
|
|
195
|
+
str(bundle.get("stage_id") or ""),
|
|
196
|
+
*[str(item) for item in bundle.get("notes") or []],
|
|
197
|
+
]
|
|
198
|
+
).lower()
|
|
199
|
+
return any(token in haystack for token in ("replication", "follow-up", "followup", "repeated-run", "repeat"))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _max_claim_state(states: list[str]) -> str:
|
|
203
|
+
if not states:
|
|
204
|
+
return "exploratory"
|
|
205
|
+
return max(states, key=lambda state: CLAIM_ORDER.get(state, CLAIM_ORDER["exploratory"]))
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _package_evidence_state(max_manifest_state: str, run_bundles: list[dict[str, Any]]) -> str:
|
|
209
|
+
derived_states = [max_manifest_state]
|
|
210
|
+
if len([bundle for bundle in run_bundles if bundle.get("manifest_artifact_id")]) >= 2 and any(
|
|
211
|
+
_bundle_has_replication_signal(bundle) for bundle in run_bundles
|
|
212
|
+
):
|
|
213
|
+
derived_states.append("replicated")
|
|
214
|
+
return _max_claim_state(derived_states)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _state_requirements(policy: dict[str, Any], state_id: str) -> list[str]:
|
|
218
|
+
for entry in policy.get("claim_states", []):
|
|
219
|
+
if entry.get("id") == state_id:
|
|
220
|
+
return [str(item) for item in entry.get("promotion_requirements", [])]
|
|
221
|
+
return []
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _find_critique_ceiling(critique_reports: list[dict[str, Any]]) -> str:
|
|
225
|
+
ceiling = "release-candidate"
|
|
226
|
+
ceiling_rank = CLAIM_ORDER[ceiling]
|
|
227
|
+
for report in critique_reports:
|
|
228
|
+
metadata = report.get("metadata", {})
|
|
229
|
+
critique_ceiling = metadata.get("critique_ceiling")
|
|
230
|
+
if isinstance(critique_ceiling, str) and critique_ceiling in CLAIM_ORDER:
|
|
231
|
+
rank = CLAIM_ORDER[critique_ceiling]
|
|
232
|
+
if rank < ceiling_rank:
|
|
233
|
+
ceiling = critique_ceiling
|
|
234
|
+
ceiling_rank = rank
|
|
235
|
+
return ceiling
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _extract_text_bullets(path: Path, *, limit: int = 4) -> list[str]:
|
|
239
|
+
bullets: list[str] = []
|
|
240
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
241
|
+
stripped = line.strip()
|
|
242
|
+
if not stripped or stripped.startswith("#"):
|
|
243
|
+
continue
|
|
244
|
+
if stripped.startswith("- "):
|
|
245
|
+
stripped = stripped[2:]
|
|
246
|
+
bullets.append(stripped)
|
|
247
|
+
if len(bullets) >= limit:
|
|
248
|
+
break
|
|
249
|
+
return bullets
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def load_package_contract(path: Path = PACKAGE_CONTRACT_PATH) -> dict[str, Any]:
|
|
253
|
+
return _load_yaml(path)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def validate_package_manifest(package: dict[str, Any], schema_path: Path = PACKAGE_SCHEMA_PATH) -> list[str]:
|
|
257
|
+
try:
|
|
258
|
+
import jsonschema
|
|
259
|
+
except ImportError:
|
|
260
|
+
return []
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
jsonschema.validate(package, _load_json(schema_path))
|
|
264
|
+
except Exception as exc: # pragma: no cover - jsonschema type depends on install
|
|
265
|
+
return [str(exc)]
|
|
266
|
+
return []
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _validate_output_root(output_root: Path, mission_state: dict[str, Any]) -> None:
|
|
270
|
+
if not _is_relative_to(output_root, WORKSPACE_ROOT / "runs"):
|
|
271
|
+
raise ValueError(f"Package output root must stay under {WORKSPACE_ROOT / 'runs'}: {output_root}")
|
|
272
|
+
repo_paths = [REPO_ROOT]
|
|
273
|
+
target_repo = mission_state.get("target_repo")
|
|
274
|
+
if isinstance(target_repo, str) and target_repo:
|
|
275
|
+
repo_paths.append(_safe_resolve(target_repo))
|
|
276
|
+
if any(_is_relative_to(output_root, repo_path) for repo_path in repo_paths):
|
|
277
|
+
raise ValueError(f"Package output root must remain outside repo trees: {output_root}")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _copy_artifact(source_path: Path, package_root: Path, copied_root_name: str) -> tuple[Path, int]:
|
|
281
|
+
package_path = package_root / _package_relative_path(source_path, copied_root_name)
|
|
282
|
+
package_path.parent.mkdir(parents=True, exist_ok=True)
|
|
283
|
+
shutil.copy2(source_path, package_path)
|
|
284
|
+
return package_path, package_path.stat().st_size
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _resolve_manifest_paths(search_roots: list[Path], mission_id: str, patterns: list[str]) -> list[Path]:
|
|
288
|
+
manifests: list[Path] = []
|
|
289
|
+
for root in search_roots:
|
|
290
|
+
if not root.exists():
|
|
291
|
+
continue
|
|
292
|
+
for pattern in patterns:
|
|
293
|
+
for path in sorted(root.glob(pattern)):
|
|
294
|
+
if not path.is_file():
|
|
295
|
+
continue
|
|
296
|
+
try:
|
|
297
|
+
manifest = _load_json(path)
|
|
298
|
+
except Exception:
|
|
299
|
+
continue
|
|
300
|
+
if manifest.get("mission_id") == mission_id:
|
|
301
|
+
manifests.append(path.resolve())
|
|
302
|
+
seen: set[Path] = set()
|
|
303
|
+
ordered: list[Path] = []
|
|
304
|
+
for path in manifests:
|
|
305
|
+
if path not in seen:
|
|
306
|
+
ordered.append(path)
|
|
307
|
+
seen.add(path)
|
|
308
|
+
return ordered
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _bundle_related_paths(manifest_path: Path, manifest: dict[str, Any], contract: dict[str, Any]) -> tuple[list[Path], list[Path]]:
|
|
312
|
+
artifact_cfg = contract.get("artifact_map", {})
|
|
313
|
+
kernel_paths: set[Path] = set()
|
|
314
|
+
critique_paths: set[Path] = set()
|
|
315
|
+
|
|
316
|
+
artifacts = manifest.get("artifacts", {})
|
|
317
|
+
if isinstance(artifacts, dict):
|
|
318
|
+
log_path = artifacts.get("log_path")
|
|
319
|
+
if isinstance(log_path, str) and log_path:
|
|
320
|
+
resolved = _resolve_existing_or_packaged_path(log_path, source_path=manifest_path)
|
|
321
|
+
if resolved.exists() and resolved.is_file():
|
|
322
|
+
kernel_paths.add(resolved)
|
|
323
|
+
for report_path in artifacts.get("report_paths", []):
|
|
324
|
+
if isinstance(report_path, str) and report_path:
|
|
325
|
+
resolved = _resolve_existing_or_packaged_path(report_path, source_path=manifest_path)
|
|
326
|
+
if resolved.exists() and resolved.is_file():
|
|
327
|
+
kernel_paths.add(resolved)
|
|
328
|
+
|
|
329
|
+
stage_artifacts = manifest.get("stage_context", {}).get("artifacts", {})
|
|
330
|
+
if isinstance(stage_artifacts, dict):
|
|
331
|
+
for raw_path in stage_artifacts.values():
|
|
332
|
+
if isinstance(raw_path, str) and raw_path:
|
|
333
|
+
resolved = _resolve_existing_or_packaged_path(raw_path, source_path=manifest_path)
|
|
334
|
+
if resolved.exists() and resolved.is_file():
|
|
335
|
+
kernel_paths.add(resolved)
|
|
336
|
+
|
|
337
|
+
output_dir = artifacts.get("output_dir") if isinstance(artifacts, dict) else None
|
|
338
|
+
if isinstance(output_dir, str) and output_dir:
|
|
339
|
+
output_root = _resolve_existing_or_packaged_path(output_dir, source_path=manifest_path)
|
|
340
|
+
for pattern in artifact_cfg.get("kernel_output_globs", []):
|
|
341
|
+
for path in sorted(output_root.glob(pattern)):
|
|
342
|
+
if path.is_file():
|
|
343
|
+
kernel_paths.add(path.resolve())
|
|
344
|
+
for pattern in artifact_cfg.get("critique_report_globs", []):
|
|
345
|
+
for path in sorted(output_root.glob(pattern)):
|
|
346
|
+
if path.is_file():
|
|
347
|
+
critique_paths.add(path.resolve())
|
|
348
|
+
|
|
349
|
+
critique_json_candidates = [
|
|
350
|
+
path
|
|
351
|
+
for path in critique_paths
|
|
352
|
+
if path.suffix.lower() == ".json"
|
|
353
|
+
]
|
|
354
|
+
for critique_json in critique_json_candidates:
|
|
355
|
+
try:
|
|
356
|
+
report = _load_json(critique_json)
|
|
357
|
+
except Exception:
|
|
358
|
+
continue
|
|
359
|
+
artifacts_payload = report.get("artifacts", {})
|
|
360
|
+
if isinstance(artifacts_payload, dict):
|
|
361
|
+
for key in ("report_markdown", "co_located_markdown", "co_located_json"):
|
|
362
|
+
raw_path = artifacts_payload.get(key)
|
|
363
|
+
if isinstance(raw_path, str) and raw_path:
|
|
364
|
+
resolved = _resolve_existing_or_packaged_path(raw_path, source_path=critique_json)
|
|
365
|
+
if resolved.exists() and resolved.is_file():
|
|
366
|
+
critique_paths.add(resolved)
|
|
367
|
+
|
|
368
|
+
return (sorted(kernel_paths), sorted(critique_paths))
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _collect_global_reports(
|
|
372
|
+
contract: dict[str, Any],
|
|
373
|
+
mission_aliases: set[str],
|
|
374
|
+
) -> list[tuple[Path, str, dict[str, Any]]]:
|
|
375
|
+
collected: list[tuple[Path, str, dict[str, Any]]] = []
|
|
376
|
+
for report_root_cfg in contract.get("artifact_map", {}).get("global_report_roots", {}).values():
|
|
377
|
+
root = _safe_resolve(report_root_cfg["root"])
|
|
378
|
+
if not root.exists():
|
|
379
|
+
continue
|
|
380
|
+
for pattern in report_root_cfg.get("patterns", []):
|
|
381
|
+
for path in sorted(root.glob(pattern)):
|
|
382
|
+
if not path.is_file():
|
|
383
|
+
continue
|
|
384
|
+
metadata: dict[str, Any] = {}
|
|
385
|
+
if path.suffix.lower() == ".json":
|
|
386
|
+
try:
|
|
387
|
+
payload = _load_json(path)
|
|
388
|
+
except Exception:
|
|
389
|
+
continue
|
|
390
|
+
mission_id = payload.get("mission_id")
|
|
391
|
+
if mission_id not in mission_aliases:
|
|
392
|
+
continue
|
|
393
|
+
if "promotion_guidance" in payload and isinstance(payload["promotion_guidance"], dict):
|
|
394
|
+
critique_ceiling = payload["promotion_guidance"].get("max_allowed_state")
|
|
395
|
+
if isinstance(critique_ceiling, str):
|
|
396
|
+
metadata["critique_ceiling"] = critique_ceiling
|
|
397
|
+
reasons = payload["promotion_guidance"].get("reasons", [])
|
|
398
|
+
metadata["promotion_reasons"] = [str(reason) for reason in reasons]
|
|
399
|
+
if isinstance(payload.get("warnings"), list):
|
|
400
|
+
metadata["warnings"] = [
|
|
401
|
+
str(item.get("message"))
|
|
402
|
+
for item in payload["warnings"]
|
|
403
|
+
if isinstance(item, dict) and item.get("message")
|
|
404
|
+
]
|
|
405
|
+
elif path.parent.name not in mission_aliases and not any(alias in path.parts for alias in mission_aliases):
|
|
406
|
+
continue
|
|
407
|
+
collected.append((path.resolve(), "critique_reports", metadata))
|
|
408
|
+
return collected
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _reference_paths_from_manifest(manifest_path: Path, manifest: dict[str, Any]) -> list[Path]:
|
|
412
|
+
candidates: list[Path] = []
|
|
413
|
+
field_paths = (
|
|
414
|
+
("behavioral_source_manifest",),
|
|
415
|
+
("localization_source",),
|
|
416
|
+
("dataset", "provenance"),
|
|
417
|
+
("evaluation", "compare_against"),
|
|
418
|
+
("stage_context", "behavioral_source_manifest"),
|
|
419
|
+
)
|
|
420
|
+
for dotted in field_paths:
|
|
421
|
+
current: Any = manifest
|
|
422
|
+
for part in dotted:
|
|
423
|
+
if not isinstance(current, dict):
|
|
424
|
+
current = None
|
|
425
|
+
break
|
|
426
|
+
current = current.get(part)
|
|
427
|
+
if isinstance(current, str) and current:
|
|
428
|
+
candidates.append(_resolve_existing_or_packaged_path(current, source_path=manifest_path))
|
|
429
|
+
return candidates
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _artifact_kind_for_path(path: Path, category: str) -> str:
|
|
433
|
+
name = path.name
|
|
434
|
+
if category == "ledgers":
|
|
435
|
+
return "ledger"
|
|
436
|
+
if category == "findings":
|
|
437
|
+
return "finding"
|
|
438
|
+
if category == "runtime_metadata":
|
|
439
|
+
if "agent_handoffs" in path.parts:
|
|
440
|
+
return "agent-handoff"
|
|
441
|
+
if path.name.startswith("mission_meta_eval"):
|
|
442
|
+
return "mission-meta-eval"
|
|
443
|
+
return "runtime-metadata"
|
|
444
|
+
if category == "mission_specs":
|
|
445
|
+
if name == "mission_state.json":
|
|
446
|
+
return "mission-state"
|
|
447
|
+
if name == "mission_summary.md":
|
|
448
|
+
return "mission-summary"
|
|
449
|
+
if name.startswith("bounded_followup_plan"):
|
|
450
|
+
return "bounded-followup-plan"
|
|
451
|
+
if name.startswith("mission_next_actions"):
|
|
452
|
+
return "mission-next-actions"
|
|
453
|
+
return "mission-spec"
|
|
454
|
+
if category == "mission_configs":
|
|
455
|
+
return "mission-config"
|
|
456
|
+
if category == "manifests":
|
|
457
|
+
return "study-manifest" if name == "study_manifest.json" else "run-manifest"
|
|
458
|
+
if category == "kernel_outputs":
|
|
459
|
+
if name.endswith(".jsonl"):
|
|
460
|
+
return "kernel-records"
|
|
461
|
+
if "metrics" in name:
|
|
462
|
+
return "kernel-metrics"
|
|
463
|
+
return "kernel-output"
|
|
464
|
+
if category == "critique_reports":
|
|
465
|
+
lowered = name.lower()
|
|
466
|
+
if "statistical_rigor" in lowered:
|
|
467
|
+
return "statistical-rigor-report"
|
|
468
|
+
if "self_optimization" in lowered:
|
|
469
|
+
return "self-optimization-report"
|
|
470
|
+
if "redteam" in lowered:
|
|
471
|
+
return "fresh-context-redteam-report"
|
|
472
|
+
if "self_correction" in lowered:
|
|
473
|
+
return "self-correction-report"
|
|
474
|
+
if "confound_guard" in lowered:
|
|
475
|
+
return "confound-guard-report"
|
|
476
|
+
return "critique-report"
|
|
477
|
+
return "artifact"
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _artifact_label(path: Path, category: str) -> str:
|
|
481
|
+
if category == "manifests":
|
|
482
|
+
return path.parent.name
|
|
483
|
+
if category == "critique_reports":
|
|
484
|
+
return path.stem
|
|
485
|
+
if category == "mission_configs" and "repos" in path.parts:
|
|
486
|
+
return path.relative_to(WORKSPACE_ROOT / "repos").as_posix()
|
|
487
|
+
if category == "mission_specs" and "repos" in path.parts:
|
|
488
|
+
return path.relative_to(WORKSPACE_ROOT / "repos").as_posix()
|
|
489
|
+
return path.name
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def package_mission_artifacts(
|
|
493
|
+
mission_state_path: Path,
|
|
494
|
+
*,
|
|
495
|
+
contract_path: Path = PACKAGE_CONTRACT_PATH,
|
|
496
|
+
output_root: Path | None = None,
|
|
497
|
+
) -> dict[str, Any]:
|
|
498
|
+
mission_state_path = mission_state_path.expanduser().resolve()
|
|
499
|
+
contract_path = contract_path.expanduser().resolve()
|
|
500
|
+
mission_state = _load_json(mission_state_path)
|
|
501
|
+
mission_root = mission_state_path.parent
|
|
502
|
+
mission_id = str(mission_state["mission_id"])
|
|
503
|
+
target_repo = _safe_resolve(mission_state["target_repo"])
|
|
504
|
+
contract = load_package_contract(contract_path)
|
|
505
|
+
evidence_policy = _load_yaml(EVIDENCE_POLICY_PATH)
|
|
506
|
+
|
|
507
|
+
resolved_output_root = _safe_resolve(output_root or contract.get("output_root", RUNS_DIR / "packages"))
|
|
508
|
+
_validate_output_root(resolved_output_root, mission_state)
|
|
509
|
+
package_root = resolved_output_root / mission_id
|
|
510
|
+
if package_root.exists():
|
|
511
|
+
_remove_tree(package_root)
|
|
512
|
+
package_root.mkdir(parents=True, exist_ok=True)
|
|
513
|
+
|
|
514
|
+
copied_root_name = str(contract.get("outputs", {}).get("copied_artifact_root", "artifacts"))
|
|
515
|
+
artifact_registry: dict[Path, dict[str, Any]] = {}
|
|
516
|
+
category_membership: dict[str, set[str]] = {category: set() for category in DEFAULT_CATEGORIES}
|
|
517
|
+
pending_links: set[tuple[Path, Path, str]] = set()
|
|
518
|
+
pending_artifact_ids: set[str] = set()
|
|
519
|
+
|
|
520
|
+
def register_artifact(
|
|
521
|
+
path: Path,
|
|
522
|
+
*,
|
|
523
|
+
category: str,
|
|
524
|
+
claim_state: str | None = None,
|
|
525
|
+
status: str | None = None,
|
|
526
|
+
metadata: dict[str, Any] | None = None,
|
|
527
|
+
lazy: bool = False,
|
|
528
|
+
) -> str:
|
|
529
|
+
resolved = path.expanduser().resolve()
|
|
530
|
+
is_file_missing = not resolved.exists() or not resolved.is_file()
|
|
531
|
+
if is_file_missing and not lazy:
|
|
532
|
+
raise FileNotFoundError(resolved)
|
|
533
|
+
artifact_id = _artifact_id_for_path(resolved)
|
|
534
|
+
record = artifact_registry.get(resolved)
|
|
535
|
+
if record is None:
|
|
536
|
+
record = {
|
|
537
|
+
"artifact_id": artifact_id,
|
|
538
|
+
"kind": _artifact_kind_for_path(resolved, category),
|
|
539
|
+
"label": _artifact_label(resolved, category),
|
|
540
|
+
"source_path": str(resolved),
|
|
541
|
+
"package_path": None,
|
|
542
|
+
"status": "pending" if is_file_missing else status,
|
|
543
|
+
"claim_state": claim_state,
|
|
544
|
+
"size_bytes": 0,
|
|
545
|
+
"metadata": dict(metadata or {}),
|
|
546
|
+
}
|
|
547
|
+
artifact_registry[resolved] = record
|
|
548
|
+
else:
|
|
549
|
+
if claim_state and not record.get("claim_state"):
|
|
550
|
+
record["claim_state"] = claim_state
|
|
551
|
+
if status and not record.get("status") and not is_file_missing:
|
|
552
|
+
record["status"] = status
|
|
553
|
+
if metadata:
|
|
554
|
+
record["metadata"].update(metadata)
|
|
555
|
+
if is_file_missing:
|
|
556
|
+
pending_artifact_ids.add(artifact_id)
|
|
557
|
+
else:
|
|
558
|
+
category_membership.setdefault(category, set()).add(artifact_id)
|
|
559
|
+
return artifact_id
|
|
560
|
+
|
|
561
|
+
def maybe_register(path: str | Path, *, category: str, metadata: dict[str, Any] | None = None) -> str | None:
|
|
562
|
+
resolved = _safe_resolve(path)
|
|
563
|
+
if not resolved.exists() or not resolved.is_file():
|
|
564
|
+
return None
|
|
565
|
+
return register_artifact(resolved, category=category, metadata=metadata)
|
|
566
|
+
|
|
567
|
+
artifact_cfg = contract.get("artifact_map", {})
|
|
568
|
+
for category, patterns in artifact_cfg.get("mission_root_sections", {}).items():
|
|
569
|
+
for pattern in patterns:
|
|
570
|
+
for matched in sorted(mission_root.glob(pattern)):
|
|
571
|
+
if matched.is_file():
|
|
572
|
+
register_artifact(matched, category=category)
|
|
573
|
+
|
|
574
|
+
for raw_doc in mission_state.get("artifacts", {}).get("docs", []):
|
|
575
|
+
maybe_register(raw_doc, category="mission_specs", metadata={"declared_by": "mission_state.artifacts.docs"})
|
|
576
|
+
for raw_config in mission_state.get("artifacts", {}).get("configs", []):
|
|
577
|
+
maybe_register(raw_config, category="mission_configs", metadata={"declared_by": "mission_state.artifacts.configs"})
|
|
578
|
+
for raw_config in mission_state.get("next_actions", {}).get("generated_configs", []):
|
|
579
|
+
maybe_register(raw_config, category="mission_configs", metadata={"declared_by": "mission_state.next_actions.generated_configs"})
|
|
580
|
+
for support_path in contract.get("supporting_contracts", []):
|
|
581
|
+
maybe_register(
|
|
582
|
+
_resolve_contract_declared_path(support_path, contract_path=contract_path),
|
|
583
|
+
category="mission_configs",
|
|
584
|
+
metadata={"declared_by": "artifact_package_contract"},
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
mission_aliases = {mission_id, mission_id.removesuffix("-mission")}
|
|
588
|
+
manifest_search_roots = [
|
|
589
|
+
WORKSPACE_ROOT / "runs" / target_repo.name,
|
|
590
|
+
mission_root,
|
|
591
|
+
]
|
|
592
|
+
manifest_paths = _resolve_manifest_paths(
|
|
593
|
+
manifest_search_roots,
|
|
594
|
+
mission_id,
|
|
595
|
+
[str(item) for item in artifact_cfg.get("manifest_patterns", [])],
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
run_bundles: list[dict[str, Any]] = []
|
|
599
|
+
for manifest_path in manifest_paths:
|
|
600
|
+
manifest = _load_json(manifest_path)
|
|
601
|
+
manifest_artifact_id = register_artifact(
|
|
602
|
+
manifest_path,
|
|
603
|
+
category="manifests",
|
|
604
|
+
claim_state=str(manifest.get("claim_state")) if manifest.get("claim_state") else None,
|
|
605
|
+
status=str(manifest.get("run", {}).get("status")) if manifest.get("run", {}).get("status") else None,
|
|
606
|
+
metadata={"loop_id": manifest.get("loop_id")},
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
for referenced_path in _reference_paths_from_manifest(manifest_path, manifest):
|
|
610
|
+
reference_category = (
|
|
611
|
+
"manifests"
|
|
612
|
+
if referenced_path.name in {"run_manifest.json", "study_manifest.json"}
|
|
613
|
+
else "mission_configs"
|
|
614
|
+
)
|
|
615
|
+
register_artifact(referenced_path, category=reference_category, lazy=True)
|
|
616
|
+
pending_links.add((manifest_path, referenced_path, "depends-on"))
|
|
617
|
+
|
|
618
|
+
kernel_paths, critique_paths = _bundle_related_paths(manifest_path, manifest, contract)
|
|
619
|
+
bundle_artifact_ids = [manifest_artifact_id]
|
|
620
|
+
critique_artifact_ids: list[str] = []
|
|
621
|
+
for kernel_path in kernel_paths:
|
|
622
|
+
kernel_id = register_artifact(kernel_path, category="kernel_outputs")
|
|
623
|
+
bundle_artifact_ids.append(kernel_id)
|
|
624
|
+
pending_links.add((manifest_path, kernel_path, "produces"))
|
|
625
|
+
for critique_path in critique_paths:
|
|
626
|
+
critique_metadata: dict[str, Any] = {}
|
|
627
|
+
if critique_path.suffix.lower() == ".json":
|
|
628
|
+
try:
|
|
629
|
+
report = _load_json(critique_path)
|
|
630
|
+
except Exception:
|
|
631
|
+
report = {}
|
|
632
|
+
if isinstance(report.get("promotion_guidance"), dict):
|
|
633
|
+
critique_ceiling = report["promotion_guidance"].get("max_allowed_state")
|
|
634
|
+
if isinstance(critique_ceiling, str):
|
|
635
|
+
critique_metadata["critique_ceiling"] = critique_ceiling
|
|
636
|
+
reasons = report["promotion_guidance"].get("reasons", [])
|
|
637
|
+
critique_metadata["promotion_reasons"] = [str(reason) for reason in reasons]
|
|
638
|
+
warnings = report.get("warnings")
|
|
639
|
+
if isinstance(warnings, list):
|
|
640
|
+
critique_metadata["warnings"] = [
|
|
641
|
+
str(item.get("message"))
|
|
642
|
+
for item in warnings
|
|
643
|
+
if isinstance(item, dict) and item.get("message")
|
|
644
|
+
]
|
|
645
|
+
critique_id = register_artifact(critique_path, category="critique_reports", metadata=critique_metadata)
|
|
646
|
+
critique_artifact_ids.append(critique_id)
|
|
647
|
+
pending_links.add((critique_path, manifest_path, "evaluates"))
|
|
648
|
+
|
|
649
|
+
metric_name, metric_value = _primary_metric(manifest)
|
|
650
|
+
run_bundles.append(
|
|
651
|
+
{
|
|
652
|
+
"bundle_id": manifest_path.parent.name,
|
|
653
|
+
"loop_id": str(manifest.get("loop_id", manifest_path.parent.name)),
|
|
654
|
+
"stage_id": _infer_manifest_stage(manifest_path, manifest),
|
|
655
|
+
"manifest_artifact_id": manifest_artifact_id,
|
|
656
|
+
"status": str(manifest.get("run", {}).get("status", "unknown")),
|
|
657
|
+
"claim_state": str(manifest.get("claim_state", "exploratory")),
|
|
658
|
+
"artifact_ids": sorted(set(bundle_artifact_ids)),
|
|
659
|
+
"critique_artifact_ids": sorted(set(critique_artifact_ids)),
|
|
660
|
+
"primary_metric": metric_value,
|
|
661
|
+
"metric_name": metric_name,
|
|
662
|
+
"summary_lines": _manifest_summary_lines(manifest),
|
|
663
|
+
"notes": [str(item) for item in manifest.get("notes", []) if str(item).strip()],
|
|
664
|
+
}
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
for report_path, category, metadata in _collect_global_reports(contract, mission_aliases):
|
|
668
|
+
register_artifact(report_path, category=category, metadata=metadata)
|
|
669
|
+
|
|
670
|
+
ledger_path = mission_root / "ledger.jsonl"
|
|
671
|
+
if ledger_path.exists():
|
|
672
|
+
ledger_artifact_id = register_artifact(ledger_path, category="ledgers")
|
|
673
|
+
for entry in _load_jsonl(ledger_path):
|
|
674
|
+
related_paths = entry.get("related_paths", [])
|
|
675
|
+
for raw_path in related_paths:
|
|
676
|
+
if not isinstance(raw_path, str):
|
|
677
|
+
continue
|
|
678
|
+
related_path = _safe_resolve(raw_path)
|
|
679
|
+
if not related_path.exists() or not related_path.is_file():
|
|
680
|
+
continue
|
|
681
|
+
if related_path.name.endswith("manifest.json"):
|
|
682
|
+
category = "manifests"
|
|
683
|
+
elif "findings" in related_path.parts:
|
|
684
|
+
category = "findings"
|
|
685
|
+
elif "runtime" in related_path.parts or "agent_handoffs" in related_path.parts:
|
|
686
|
+
category = "runtime_metadata"
|
|
687
|
+
elif "report" in related_path.name or "statistical_rigor" in related_path.name:
|
|
688
|
+
category = "critique_reports"
|
|
689
|
+
elif related_path.suffix.lower() in {".yaml", ".yml", ".json"}:
|
|
690
|
+
category = "mission_configs"
|
|
691
|
+
else:
|
|
692
|
+
category = "mission_specs"
|
|
693
|
+
register_artifact(related_path, category=category)
|
|
694
|
+
pending_links.add((ledger_path, related_path, "ledger-related"))
|
|
695
|
+
if category == "critique_reports":
|
|
696
|
+
pending_links.add((related_path, ledger_path, "recorded-in"))
|
|
697
|
+
category_membership["ledgers"].add(ledger_artifact_id)
|
|
698
|
+
|
|
699
|
+
disappeared_paths = [
|
|
700
|
+
path
|
|
701
|
+
for path in artifact_registry
|
|
702
|
+
if (not path.exists() or not path.is_file())
|
|
703
|
+
and artifact_registry[path]["artifact_id"] not in pending_artifact_ids
|
|
704
|
+
]
|
|
705
|
+
for path in disappeared_paths:
|
|
706
|
+
artifact_id = artifact_registry[path]["artifact_id"]
|
|
707
|
+
del artifact_registry[path]
|
|
708
|
+
for artifact_ids in category_membership.values():
|
|
709
|
+
artifact_ids.discard(artifact_id)
|
|
710
|
+
|
|
711
|
+
missing_required_artifacts: list[str] = []
|
|
712
|
+
required_filenames = {str(item) for item in contract.get("required_artifacts", {}).get("filenames", [])}
|
|
713
|
+
existing_filenames = {path.name for path in artifact_registry}
|
|
714
|
+
for filename in sorted(required_filenames):
|
|
715
|
+
if filename not in existing_filenames:
|
|
716
|
+
missing_required_artifacts.append(filename)
|
|
717
|
+
required_categories = [str(item) for item in contract.get("required_artifacts", {}).get("categories", [])]
|
|
718
|
+
for category in required_categories:
|
|
719
|
+
if not category_membership.get(category):
|
|
720
|
+
missing_required_artifacts.append(f"category:{category}")
|
|
721
|
+
all_required_artifacts_present = not missing_required_artifacts
|
|
722
|
+
missing_required_artifacts_summary = (
|
|
723
|
+
f"Missing required mission artifacts: {', '.join(missing_required_artifacts)}"
|
|
724
|
+
if missing_required_artifacts
|
|
725
|
+
else None
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
digest = hashlib.sha256()
|
|
729
|
+
for source_path in sorted(artifact_registry):
|
|
730
|
+
if artifact_registry[source_path]["artifact_id"] in pending_artifact_ids:
|
|
731
|
+
continue
|
|
732
|
+
digest.update(str(source_path).encode("utf-8"))
|
|
733
|
+
digest.update(b"\0")
|
|
734
|
+
digest.update(source_path.read_bytes())
|
|
735
|
+
digest.update(b"\0")
|
|
736
|
+
package_digest = digest.hexdigest()
|
|
737
|
+
|
|
738
|
+
for source_path in sorted(artifact_registry):
|
|
739
|
+
if artifact_registry[source_path]["artifact_id"] in pending_artifact_ids:
|
|
740
|
+
continue
|
|
741
|
+
package_path, size_bytes = _copy_artifact(source_path, package_root, copied_root_name)
|
|
742
|
+
artifact_registry[source_path]["package_path"] = str(package_path.relative_to(package_root))
|
|
743
|
+
artifact_registry[source_path]["size_bytes"] = size_bytes
|
|
744
|
+
|
|
745
|
+
source_to_id = {source: record["artifact_id"] for source, record in artifact_registry.items()}
|
|
746
|
+
cross_links: list[dict[str, str]] = []
|
|
747
|
+
for source_path, target_path, relationship in sorted(pending_links, key=lambda item: (str(item[0]), str(item[1]), item[2])):
|
|
748
|
+
source_id = source_to_id.get(source_path.resolve())
|
|
749
|
+
target_id = source_to_id.get(target_path.resolve())
|
|
750
|
+
if source_id and target_id:
|
|
751
|
+
cross_links.append(
|
|
752
|
+
{
|
|
753
|
+
"source_artifact_id": source_id,
|
|
754
|
+
"target_artifact_id": target_id,
|
|
755
|
+
"relationship": relationship,
|
|
756
|
+
}
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
artifacts = sorted(
|
|
760
|
+
(record for record in artifact_registry.values() if record["artifact_id"] not in pending_artifact_ids),
|
|
761
|
+
key=lambda item: item["source_path"],
|
|
762
|
+
)
|
|
763
|
+
pending_artifacts = sorted(
|
|
764
|
+
(record for record in artifact_registry.values() if record["artifact_id"] in pending_artifact_ids),
|
|
765
|
+
key=lambda item: item["source_path"],
|
|
766
|
+
)
|
|
767
|
+
artifact_map = {
|
|
768
|
+
category: sorted(category_membership.get(category, set()))
|
|
769
|
+
for category in DEFAULT_CATEGORIES
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
critique_reports = [
|
|
773
|
+
artifact
|
|
774
|
+
for artifact in artifacts
|
|
775
|
+
if artifact["artifact_id"] in set(artifact_map["critique_reports"])
|
|
776
|
+
]
|
|
777
|
+
manifest_claim_counts: dict[str, int] = {}
|
|
778
|
+
for bundle in run_bundles:
|
|
779
|
+
manifest_claim_counts[bundle["claim_state"]] = manifest_claim_counts.get(bundle["claim_state"], 0) + 1
|
|
780
|
+
max_manifest_state = _max_claim_state(list(manifest_claim_counts))
|
|
781
|
+
critique_ceiling = _find_critique_ceiling(critique_reports)
|
|
782
|
+
evidence_state = _package_evidence_state(max_manifest_state, run_bundles)
|
|
783
|
+
package_claim_state = min(
|
|
784
|
+
(evidence_state, critique_ceiling),
|
|
785
|
+
key=lambda state: CLAIM_ORDER.get(state, CLAIM_ORDER["exploratory"]),
|
|
786
|
+
)
|
|
787
|
+
replication_evidence = _build_replication_evidence(run_bundles)
|
|
788
|
+
next_state = _next_claim_state(package_claim_state)
|
|
789
|
+
critique_reasons: list[str] = []
|
|
790
|
+
critique_warnings: list[str] = []
|
|
791
|
+
for artifact in critique_reports:
|
|
792
|
+
metadata = artifact.get("metadata", {})
|
|
793
|
+
for reason in metadata.get("promotion_reasons", []):
|
|
794
|
+
if reason not in critique_reasons:
|
|
795
|
+
critique_reasons.append(reason)
|
|
796
|
+
for warning in metadata.get("warnings", []):
|
|
797
|
+
if warning not in critique_warnings:
|
|
798
|
+
critique_warnings.append(warning)
|
|
799
|
+
|
|
800
|
+
top_bundles = sorted(
|
|
801
|
+
run_bundles,
|
|
802
|
+
key=lambda bundle: (
|
|
803
|
+
bundle["primary_metric"] if bundle["primary_metric"] is not None else float("-inf"),
|
|
804
|
+
bundle["loop_id"],
|
|
805
|
+
),
|
|
806
|
+
reverse=True,
|
|
807
|
+
)[:3]
|
|
808
|
+
finding_ids = artifact_map["findings"]
|
|
809
|
+
finding_artifacts = [artifact for artifact in artifacts if artifact["artifact_id"] in set(finding_ids)]
|
|
810
|
+
finding_bullets: list[str] = []
|
|
811
|
+
for finding in finding_artifacts:
|
|
812
|
+
finding_bullets.extend(_extract_text_bullets(Path(finding["source_path"]), limit=2))
|
|
813
|
+
if len(finding_bullets) >= 4:
|
|
814
|
+
break
|
|
815
|
+
has_documented_caveats = bool(finding_artifacts or critique_reasons or critique_warnings)
|
|
816
|
+
paper_blockers: list[str] = []
|
|
817
|
+
if CLAIM_ORDER.get(package_claim_state, CLAIM_ORDER["exploratory"]) < CLAIM_ORDER["replicated"]:
|
|
818
|
+
paper_blockers.append("replicated evidence")
|
|
819
|
+
if not has_documented_caveats:
|
|
820
|
+
paper_blockers.append("documented caveats")
|
|
821
|
+
paper_blockers.append("human approval")
|
|
822
|
+
if next_state == "paper-candidate" and CLAIM_ORDER.get(package_claim_state, CLAIM_ORDER["exploratory"]) >= CLAIM_ORDER["replicated"]:
|
|
823
|
+
paper_blockers = [blocker for blocker in paper_blockers if blocker != "replicated evidence"]
|
|
824
|
+
release_blockers: list[str] = []
|
|
825
|
+
paper_candidate_equivalent = (
|
|
826
|
+
CLAIM_ORDER.get(package_claim_state, CLAIM_ORDER["exploratory"]) >= CLAIM_ORDER["paper-candidate"]
|
|
827
|
+
or (
|
|
828
|
+
package_claim_state == "replicated"
|
|
829
|
+
and all(blocker not in {"replicated evidence", "documented caveats"} for blocker in paper_blockers)
|
|
830
|
+
)
|
|
831
|
+
)
|
|
832
|
+
if not paper_candidate_equivalent:
|
|
833
|
+
release_blockers.append("paper-candidate evidence or equivalent rigor")
|
|
834
|
+
release_blockers.extend(["provenance and licensing review", "human approval"])
|
|
835
|
+
|
|
836
|
+
operator_key_ids = artifact_map["mission_specs"][:2] + artifact_map["findings"][:1] + artifact_map["manifests"][:2]
|
|
837
|
+
operator_bullets = [
|
|
838
|
+
f"Package root: {package_root}",
|
|
839
|
+
f"Current phase: {mission_state.get('current_phase')} ({mission_state.get('status')})",
|
|
840
|
+
f"Packaged {len(artifacts)} artifacts across {len(run_bundles)} manifest bundles.",
|
|
841
|
+
]
|
|
842
|
+
if missing_required_artifacts_summary:
|
|
843
|
+
operator_bullets.append(missing_required_artifacts_summary)
|
|
844
|
+
next_actions = mission_state.get("next_actions", {})
|
|
845
|
+
if isinstance(next_actions.get("summary"), str):
|
|
846
|
+
operator_bullets.append(f"Next-actions summary: {next_actions['summary']}")
|
|
847
|
+
for action in next_actions.get("actions", [])[:4]:
|
|
848
|
+
if isinstance(action, dict):
|
|
849
|
+
operator_bullets.append(f"{action.get('role')}: {action.get('task')}")
|
|
850
|
+
|
|
851
|
+
paper_bullets = [f"Conservative claim posture: {package_claim_state}."]
|
|
852
|
+
if top_bundles:
|
|
853
|
+
for bundle in top_bundles:
|
|
854
|
+
metric_display = (
|
|
855
|
+
f"{bundle['metric_name']}={bundle['primary_metric']:.6g}"
|
|
856
|
+
if bundle["metric_name"] and bundle["primary_metric"] is not None
|
|
857
|
+
else "metric unavailable"
|
|
858
|
+
)
|
|
859
|
+
paper_bullets.append(
|
|
860
|
+
f"{bundle['loop_id']} ({bundle['stage_id']}, {bundle['claim_state']}): {metric_display}."
|
|
861
|
+
)
|
|
862
|
+
paper_bullets.extend(finding_bullets[:3])
|
|
863
|
+
paper_bullets.extend(critique_reasons[:3])
|
|
864
|
+
|
|
865
|
+
release_bullets = [f"Package claim state: {package_claim_state}.", f"Critique ceiling: {critique_ceiling}."]
|
|
866
|
+
release_bullets.append(
|
|
867
|
+
"Replication evidence: "
|
|
868
|
+
f"{replication_evidence['total_manifests']} manifests across "
|
|
869
|
+
f"{replication_evidence['independent_runs']} run groups."
|
|
870
|
+
)
|
|
871
|
+
if evidence_state != max_manifest_state:
|
|
872
|
+
release_bullets.append(
|
|
873
|
+
f"Package-level evidence raises the manifest floor from {max_manifest_state} to {evidence_state}."
|
|
874
|
+
)
|
|
875
|
+
release_bullets.extend(release_blockers[:3])
|
|
876
|
+
release_bullets.extend(critique_warnings[:3])
|
|
877
|
+
if missing_required_artifacts_summary:
|
|
878
|
+
release_bullets.append(missing_required_artifacts_summary)
|
|
879
|
+
mission_scheduler = mission_state.get("mission_scheduler")
|
|
880
|
+
if isinstance(mission_scheduler, dict) and mission_scheduler.get("scheduler_id"):
|
|
881
|
+
release_bullets.append(
|
|
882
|
+
"Scheduler posture: "
|
|
883
|
+
f"{mission_scheduler.get('scheduler_id')}={mission_scheduler.get('scheduler_status', 'unknown')}, "
|
|
884
|
+
f"remaining_budget={mission_scheduler.get('remaining_budget', 'n/a')}."
|
|
885
|
+
)
|
|
886
|
+
mission_memory_path = mission_root / "mission_memory.json"
|
|
887
|
+
if mission_memory_path.exists():
|
|
888
|
+
mission_memory = _load_json(mission_memory_path)
|
|
889
|
+
retrieved_research = (
|
|
890
|
+
mission_memory.get("retrieved_research_context")
|
|
891
|
+
if isinstance(mission_memory.get("retrieved_research_context"), dict)
|
|
892
|
+
else {}
|
|
893
|
+
)
|
|
894
|
+
if retrieved_research:
|
|
895
|
+
release_bullets.append(
|
|
896
|
+
"Indexed memory context: "
|
|
897
|
+
f"{len(retrieved_research.get('matches', [])) if isinstance(retrieved_research.get('matches'), list) else 0} "
|
|
898
|
+
f"match(es) for `{retrieved_research.get('query', '')}`."
|
|
899
|
+
)
|
|
900
|
+
platform_expansion = mission_state.get("platform_expansion")
|
|
901
|
+
if isinstance(platform_expansion, dict):
|
|
902
|
+
surfaces = platform_expansion.get("surfaces")
|
|
903
|
+
if isinstance(surfaces, dict) and surfaces:
|
|
904
|
+
release_bullets.append(
|
|
905
|
+
"Platform surfaces: "
|
|
906
|
+
+ ", ".join(
|
|
907
|
+
f"{surface_id}={surface.get('status', 'unknown')}"
|
|
908
|
+
for surface_id, surface in sorted(surfaces.items())
|
|
909
|
+
if isinstance(surface, dict)
|
|
910
|
+
)
|
|
911
|
+
+ "."
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
manifest_name = str(contract.get("outputs", {}).get("manifest_json", "mission_artifact_package.json"))
|
|
915
|
+
summary_name = str(contract.get("outputs", {}).get("summary_markdown", "mission_artifact_package.md"))
|
|
916
|
+
manifest_path = package_root / manifest_name
|
|
917
|
+
summary_path = package_root / summary_name
|
|
918
|
+
|
|
919
|
+
package = {
|
|
920
|
+
"schema_version": 1,
|
|
921
|
+
"package_id": f"{mission_id}-{package_claim_state}-package",
|
|
922
|
+
"mission_id": mission_id,
|
|
923
|
+
"package_root": str(package_root),
|
|
924
|
+
"package_digest": package_digest,
|
|
925
|
+
"mission": {
|
|
926
|
+
"title": mission_state.get("title", ""),
|
|
927
|
+
"objective": mission_state.get("objective", ""),
|
|
928
|
+
"current_phase": mission_state.get("current_phase", ""),
|
|
929
|
+
"status": mission_state.get("status", ""),
|
|
930
|
+
"target_repo": str(target_repo),
|
|
931
|
+
"roles": list(mission_state.get("roles", [])),
|
|
932
|
+
},
|
|
933
|
+
"claim_summary": {
|
|
934
|
+
"package_claim_state": package_claim_state,
|
|
935
|
+
"manifest_claim_counts": manifest_claim_counts,
|
|
936
|
+
"critique_ceiling": critique_ceiling,
|
|
937
|
+
"promotion_requirements": _state_requirements(evidence_policy, package_claim_state),
|
|
938
|
+
"paper_candidate_blockers": (
|
|
939
|
+
([missing_required_artifacts_summary] if missing_required_artifacts_summary else [])
|
|
940
|
+
+ paper_blockers
|
|
941
|
+
+ critique_reasons
|
|
942
|
+
),
|
|
943
|
+
"release_candidate_blockers": (
|
|
944
|
+
([missing_required_artifacts_summary] if missing_required_artifacts_summary else [])
|
|
945
|
+
+ release_blockers
|
|
946
|
+
+ critique_reasons
|
|
947
|
+
),
|
|
948
|
+
},
|
|
949
|
+
"artifacts": artifacts,
|
|
950
|
+
"artifact_map": artifact_map,
|
|
951
|
+
"replication_evidence": replication_evidence,
|
|
952
|
+
"run_bundles": sorted(run_bundles, key=lambda item: item["loop_id"]),
|
|
953
|
+
"cross_links": cross_links,
|
|
954
|
+
"summary": {
|
|
955
|
+
"operator_handoff": {
|
|
956
|
+
"headline": "Operator handoff package",
|
|
957
|
+
"bullets": operator_bullets,
|
|
958
|
+
"key_artifact_ids": operator_key_ids,
|
|
959
|
+
},
|
|
960
|
+
"paper_drafting": {
|
|
961
|
+
"headline": "Paper drafting posture",
|
|
962
|
+
"bullets": paper_bullets,
|
|
963
|
+
"key_artifact_ids": [bundle["manifest_artifact_id"] for bundle in top_bundles],
|
|
964
|
+
},
|
|
965
|
+
"release_review": {
|
|
966
|
+
"headline": "Release review posture",
|
|
967
|
+
"bullets": release_bullets,
|
|
968
|
+
"key_artifact_ids": artifact_map["critique_reports"][:3],
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
"checks": {
|
|
972
|
+
"copy_complete": True,
|
|
973
|
+
"outside_repo_outputs": True,
|
|
974
|
+
"all_required_artifacts_present": all_required_artifacts_present,
|
|
975
|
+
"missing_required_artifacts": missing_required_artifacts,
|
|
976
|
+
"validation_errors": [],
|
|
977
|
+
"artifact_count_by_category": {
|
|
978
|
+
category: len(ids)
|
|
979
|
+
for category, ids in artifact_map.items()
|
|
980
|
+
},
|
|
981
|
+
"pending_downstream_artifacts": [record["source_path"] for record in pending_artifacts],
|
|
982
|
+
},
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
release_policy = load_release_candidate_policy()
|
|
986
|
+
release_review = build_release_candidate_review(
|
|
987
|
+
package,
|
|
988
|
+
package_manifest_path=manifest_path,
|
|
989
|
+
policy=release_policy,
|
|
990
|
+
)
|
|
991
|
+
release_bullets.insert(0, f"Release automation decision: {release_review['decision']}.")
|
|
992
|
+
if release_review["missing_approvals"]:
|
|
993
|
+
release_bullets.append(
|
|
994
|
+
"Missing approvals: " + ", ".join(sorted(release_review["missing_approvals"])) + "."
|
|
995
|
+
)
|
|
996
|
+
if release_review["failed_gate_ids"]:
|
|
997
|
+
release_bullets.append(
|
|
998
|
+
"Failed gates: " + ", ".join(release_review["failed_gate_ids"][:3]) + "."
|
|
999
|
+
)
|
|
1000
|
+
package["summary"]["release_review"]["bullets"] = release_bullets
|
|
1001
|
+
package["release_automation"] = build_package_release_automation(release_review)
|
|
1002
|
+
|
|
1003
|
+
validation_errors = validate_package_manifest(package)
|
|
1004
|
+
package["checks"]["validation_errors"] = validation_errors
|
|
1005
|
+
if validation_errors:
|
|
1006
|
+
raise RuntimeError("Mission artifact package failed schema validation: " + "; ".join(validation_errors))
|
|
1007
|
+
|
|
1008
|
+
manifest_path.write_text(json.dumps(package, indent=2) + "\n", encoding="utf-8")
|
|
1009
|
+
|
|
1010
|
+
markdown_lines = [
|
|
1011
|
+
"# Mission artifact package",
|
|
1012
|
+
"",
|
|
1013
|
+
f"- mission_id: `{mission_id}`",
|
|
1014
|
+
f"- package_id: `{package['package_id']}`",
|
|
1015
|
+
f"- package_claim_state: `{package_claim_state}`",
|
|
1016
|
+
f"- package_digest: `{package_digest}`",
|
|
1017
|
+
f"- package_root: `{package_root}`",
|
|
1018
|
+
f"- target_repo: `{target_repo}`",
|
|
1019
|
+
"",
|
|
1020
|
+
"## Operator handoff",
|
|
1021
|
+
"",
|
|
1022
|
+
]
|
|
1023
|
+
markdown_lines.extend(f"- {line}" for line in operator_bullets)
|
|
1024
|
+
markdown_lines.extend(["", "## Paper drafting", ""])
|
|
1025
|
+
markdown_lines.extend(f"- {line}" for line in paper_bullets)
|
|
1026
|
+
markdown_lines.extend(["", "## Release review", ""])
|
|
1027
|
+
markdown_lines.extend(f"- {line}" for line in release_bullets)
|
|
1028
|
+
if operator_key_ids:
|
|
1029
|
+
markdown_lines.extend(["", "## Key artifact ids", ""])
|
|
1030
|
+
markdown_lines.extend(f"- `{artifact_id}`" for artifact_id in operator_key_ids)
|
|
1031
|
+
summary_path.write_text("\n".join(markdown_lines) + "\n", encoding="utf-8")
|
|
1032
|
+
|
|
1033
|
+
release_review_result = materialize_release_candidate_review(
|
|
1034
|
+
release_review,
|
|
1035
|
+
package_root=package_root,
|
|
1036
|
+
policy=release_policy,
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
return {
|
|
1040
|
+
"package_root": package_root,
|
|
1041
|
+
"manifest_path": manifest_path,
|
|
1042
|
+
"summary_path": summary_path,
|
|
1043
|
+
"package": package,
|
|
1044
|
+
"release_review_path": release_review_result["review_json"],
|
|
1045
|
+
"release_review_markdown_path": release_review_result["review_markdown"],
|
|
1046
|
+
}
|