CIE-Unified

git clone 

commit
419ae8d
parent
164c06a
author
codex@macbookpro
date
2026-03-31 17:36:55 +0800 CST
branch-a: task04 unified validation + reporting
7 files changed,  +2053, -2
A cie/validation.py
+476, -0
  1@@ -0,0 +1,476 @@
  2+from __future__ import annotations
  3+
  4+import argparse
  5+import json
  6+import subprocess
  7+import sys
  8+from pathlib import Path
  9+from typing import Any, Dict, Iterable, List, Sequence
 10+
 11+from .runtime import CIERuntime, REQUIRED_SNAPSHOT_KEYS
 12+
 13+BASE_COMMIT = "164c06af63812c69cab32d8f8a6c770b96f38ef6"
 14+DEFAULT_BRANCH = "branch-a/task04-validation-reporting"
 15+REPO_ROOT = Path(__file__).resolve().parent.parent
 16+DEFAULT_JSON_REPORT_PATH = REPO_ROOT / "reports" / "2026-03-31_task04_branch_a_validation.json"
 17+DEFAULT_MARKDOWN_REPORT_PATH = REPO_ROOT / "reports" / "2026-03-31_task04_branch_a_validation.md"
 18+REPORT_TOP_LEVEL_KEYS = (
 19+    "branch",
 20+    "base_commit",
 21+    "runtime_summary",
 22+    "interface_checks",
 23+    "smoke_checks",
 24+    "dynamics_checks",
 25+    "sedimentation_checks",
 26+    "snapshot_checks",
 27+    "known_limitations",
 28+    "overall_status",
 29+)
 30+LOCKED_INTERFACE = (
 31+    "ingest",
 32+    "step",
 33+    "emit",
 34+    "commit_feedback",
 35+    "snapshot_state",
 36+    "reset_session",
 37+)
 38+OUTPUT_MODES = ("full", "degraded", "minimal")
 39+
 40+
 41+def _git_stdout(args: Sequence[str], fallback: str) -> str:
 42+    try:
 43+        completed = subprocess.run(
 44+            ["git", *args],
 45+            cwd=REPO_ROOT,
 46+            check=True,
 47+            capture_output=True,
 48+            text=True,
 49+        )
 50+    except (FileNotFoundError, subprocess.CalledProcessError):
 51+        return fallback
 52+    value = completed.stdout.strip()
 53+    return value or fallback
 54+
 55+
 56+def _current_branch() -> str:
 57+    return _git_stdout(["rev-parse", "--abbrev-ref", "HEAD"], DEFAULT_BRANCH)
 58+
 59+
 60+def _check(name: str, ok: bool, detail: str, **extra: Any) -> Dict[str, Any]:
 61+    entry: Dict[str, Any] = {
 62+        "name": name,
 63+        "status": "pass" if ok else "fail",
 64+        "detail": detail,
 65+    }
 66+    entry.update(extra)
 67+    return entry
 68+
 69+
 70+def _section(checks: List[Dict[str, Any]], **summary: Any) -> Dict[str, Any]:
 71+    failed = sum(1 for item in checks if item["status"] != "pass")
 72+    return {
 73+        "status": "pass" if failed == 0 else "fail",
 74+        "passed": len(checks) - failed,
 75+        "failed": failed,
 76+        "checks": checks,
 77+        "summary": summary,
 78+    }
 79+
 80+
 81+def _section_statuses(report: Dict[str, Any]) -> List[str]:
 82+    return [
 83+        report["interface_checks"]["status"],
 84+        report["smoke_checks"]["status"],
 85+        report["dynamics_checks"]["status"],
 86+        report["sedimentation_checks"]["status"],
 87+        report["snapshot_checks"]["status"],
 88+    ]
 89+
 90+
 91+def _interface_checks() -> Dict[str, Any]:
 92+    runtime = CIERuntime()
 93+    snapshot = runtime.snapshot_state()
 94+    checks = [
 95+        _check(
 96+            "runtime_initializes",
 97+            snapshot["output_mode"] == "minimal" and snapshot["active_region"] == [],
 98+            "A fresh runtime exposes minimal output mode and no active region.",
 99+            observed_output_mode=snapshot["output_mode"],
100+            observed_active_region=snapshot["active_region"],
101+        ),
102+        _check(
103+            "locked_interface_presence",
104+            all(callable(getattr(runtime, name, None)) for name in LOCKED_INTERFACE),
105+            "Branch A exposes the locked runtime interface required by the spec.",
106+            interface=list(LOCKED_INTERFACE),
107+        ),
108+    ]
109+    return _section(
110+        checks,
111+        snapshot_keys=sorted(snapshot),
112+        required_snapshot_keys=sorted(REQUIRED_SNAPSHOT_KEYS),
113+    )
114+
115+
116+def _smoke_checks() -> Dict[str, Any]:
117+    runtime = CIERuntime(capacity_limit=8.0)
118+    initial = runtime.snapshot_state()
119+    queued = runtime.ingest(
120+        "graph native observability",
121+        context="branch a validation smoke",
122+        anchors="anchor",
123+    )
124+    stepped = runtime.step(2)
125+    output = runtime.emit()
126+    queued_feedback = runtime.snapshot_state()
127+    after_feedback = runtime.step()
128+    checks = [
129+        _check(
130+            "ingest_queues_signal",
131+            bool(queued["queued_tokens"]) and bool(queued["queued_anchors"]),
132+            "Ingest stores external tokens and anchor hints before the step loop runs.",
133+            queued_tokens=queued["queued_tokens"],
134+            queued_anchors=queued["queued_anchors"],
135+        ),
136+        _check(
137+            "step_materializes_graph_state",
138+            stepped["phi_summary"]["node_count"] > initial["phi_summary"]["node_count"]
139+            and stepped["mu_summary"]["total_activation"] > 0.0
140+            and stepped["J_summary"]["edge_count"] > 0,
141+            "Stepping from a queued input creates observable phi/mu/J state.",
142+            phi_summary=stepped["phi_summary"],
143+            mu_summary=stepped["mu_summary"],
144+            J_summary=stepped["J_summary"],
145+        ),
146+        _check(
147+            "emit_queues_output_feedback",
148+            queued_feedback["feedback_effect"].get("source") == "emit"
149+            and bool(queued_feedback["feedback_effect"].get("queued_tokens")),
150+            "Emit creates a real output-to-input feedback signal rather than a log-only artifact.",
151+            emitted_output=output,
152+            feedback_effect=queued_feedback["feedback_effect"],
153+        ),
154+        _check(
155+            "feedback_changes_later_state",
156+            after_feedback["feedback_effect"].get("last_applied_step") == runtime.state.step_index
157+            and bool(after_feedback["feedback_effect"].get("applied_tokens")),
158+            "Queued feedback is applied on the next step and changes later runtime state.",
159+            feedback_effect=after_feedback["feedback_effect"],
160+        ),
161+    ]
162+    return _section(
163+        checks,
164+        emitted_output=output,
165+        bound_ability_core=after_feedback["bound_ability_core"],
166+        active_region=after_feedback["active_region"],
167+    )
168+
169+
170+def _dynamics_checks() -> Dict[str, Any]:
171+    evolution_runtime = CIERuntime(capacity_limit=8.0)
172+    evolution_runtime.ingest("alpha beta alpha", context="gamma", anchors="anchor")
173+    step_one = evolution_runtime.step()
174+    step_three = evolution_runtime.step(2)
175+
176+    full_runtime = CIERuntime(capacity_limit=10.0)
177+    full_runtime.ingest("focus focus focus", anchors="anchor")
178+    full_runtime.step(2)
179+    full_mode = full_runtime.snapshot_state()["output_mode"]
180+    full_output = full_runtime.emit()
181+
182+    degraded_runtime = CIERuntime(capacity_limit=6.0)
183+    degraded_runtime.ingest("alpha beta gamma delta epsilon", anchors="anchor")
184+    degraded_runtime.step(2)
185+    degraded_mode = degraded_runtime.snapshot_state()["output_mode"]
186+    degraded_output = degraded_runtime.emit()
187+
188+    minimal_runtime = CIERuntime(capacity_limit=0.9)
189+    minimal_runtime.ingest("alpha beta gamma delta epsilon", anchors="anchor")
190+    minimal_runtime.step(2)
191+    minimal_mode = minimal_runtime.snapshot_state()["output_mode"]
192+    minimal_output = minimal_runtime.emit()
193+
194+    decay_runtime = CIERuntime(capacity_limit=10.0)
195+    for _ in range(4):
196+        decay_runtime.ingest("stale alpha beta", anchors="anchor")
197+        decay_runtime.step()
198+    decay_runtime.reset_session()
199+    decay_snapshot = decay_runtime.step(6)
200+
201+    observed_modes = sorted({full_mode, degraded_mode, minimal_mode})
202+    checks = [
203+        _check(
204+            "phi_mu_J_are_observable",
205+            step_one["phi_summary"]["total_potential"] != step_three["phi_summary"]["total_potential"]
206+            and step_one["mu_summary"]["total_activation"] != step_three["mu_summary"]["total_activation"]
207+            and step_one["J_summary"]["total_flow"] != step_three["J_summary"]["total_flow"],
208+            "Multi-step dynamics produce visible changes across phi, mu, and J.",
209+            step_one=step_one,
210+            step_three=step_three,
211+        ),
212+        _check(
213+            "homing_signals_are_populated",
214+            step_three["bound_ability_core"] is not None
215+            and step_three["anchor_pull"] > 0.0
216+            and 0.0 <= step_three["drift_score"] <= 1.0,
217+            "The runtime exposes bound_ability_core, anchor_pull, and drift_score as active homing signals.",
218+            bound_ability_core=step_three["bound_ability_core"],
219+            anchor_pull=step_three["anchor_pull"],
220+            drift_score=step_three["drift_score"],
221+        ),
222+        _check(
223+            "decay_and_forgetting_are_visible",
224+            bool(decay_snapshot["decay_events"])
225+            and any(event["kind"] == "sedimentation_demote" for event in decay_snapshot["decay_events"]),
226+            "Decay/forgetting remains real and observable through decay events and stage demotion.",
227+            decay_events=decay_snapshot["decay_events"][-6:],
228+        ),
229+        _check(
230+            "degraded_output_modes_exist",
231+            observed_modes == sorted(OUTPUT_MODES)
232+            and full_output.startswith("full:")
233+            and degraded_output.startswith("degraded:")
234+            and minimal_output.startswith("minimal:"),
235+            "The runtime emits full, degraded, and minimal outputs under different runtime conditions.",
236+            observed_modes=observed_modes,
237+            outputs={
238+                "full": full_output,
239+                "degraded": degraded_output,
240+                "minimal": minimal_output,
241+            },
242+        ),
243+    ]
244+    return _section(
245+        checks,
246+        observed_modes=observed_modes,
247+        total_decay_events=len(decay_snapshot["decay_events"]),
248+    )
249+
250+
251+def _sedimentation_checks() -> Dict[str, Any]:
252+    runtime = CIERuntime(capacity_limit=10.0)
253+    for _ in range(4):
254+        runtime.ingest("alpha beta alpha", anchors="anchor")
255+        runtime.step()
256+    snapshot = runtime.snapshot_state()
257+    alpha_trace = [
258+        event for event in snapshot["sedimentation_trace"] if event["node"] == "alpha"
259+    ]
260+    alpha_candidate = next(
261+        item for item in snapshot["skill_belt_candidates"] if item["node"] == "alpha"
262+    )
263+    checks = [
264+        _check(
265+            "sedimentation_trace_exists",
266+            bool(snapshot["sedimentation_trace"]),
267+            "Sedimentation history is exported as explicit runtime trace entries.",
268+            trace_tail=snapshot["sedimentation_trace"][-5:],
269+        ),
270+        _check(
271+            "stage_progression_matches_locked_path",
272+            [event["to"] for event in alpha_trace] == ["experience", "skill_belt", "ability_core"],
273+            "Repeated activation follows the locked memory -> experience -> skill_belt -> ability_core path.",
274+            alpha_trace=alpha_trace,
275+        ),
276+        _check(
277+            "skill_belt_candidates_have_evidence",
278+            alpha_candidate["stage"] in {"skill_belt", "ability_core"}
279+            and alpha_candidate["stable_steps"] >= 2,
280+            "Skill-belt candidates are backed by repeated activation, stability, and flow evidence.",
281+            alpha_candidate=alpha_candidate,
282+        ),
283+        _check(
284+            "merge_events_are_recorded",
285+            bool(snapshot["merge_events"])
286+            and any(event["node"] == "alpha" for event in snapshot["merge_events"]),
287+            "Stable skill-belt structures can produce explicit merge events into ability-core structures.",
288+            merge_events=snapshot["merge_events"],
289+        ),
290+    ]
291+    return _section(
292+        checks,
293+        experience_regions=snapshot["experience_regions"],
294+        bound_ability_core=snapshot["bound_ability_core"],
295+    )
296+
297+
298+def _snapshot_checks() -> Dict[str, Any]:
299+    runtime = CIERuntime(capacity_limit=8.0)
300+    runtime.ingest("branch a graph native feedback", context="runtime state", anchors="anchor")
301+    runtime.step(2)
302+    runtime.emit()
303+    snapshot = runtime.step()
304+    checks = [
305+        _check(
306+            "locked_snapshot_fields_present",
307+            set(snapshot) == REQUIRED_SNAPSHOT_KEYS,
308+            "snapshot_state returns the locked comparable field set.",
309+            observed_keys=sorted(snapshot),
310+            required_keys=sorted(REQUIRED_SNAPSHOT_KEYS),
311+        ),
312+        _check(
313+            "summary_fields_are_meaningful",
314+            snapshot["phi_summary"]["node_count"] > 0
315+            and snapshot["mu_summary"]["active_count"] > 0
316+            and snapshot["J_summary"]["edge_count"] > 0,
317+            "phi_summary, mu_summary, and J_summary expose non-empty observable summaries after activity.",
318+            phi_summary=snapshot["phi_summary"],
319+            mu_summary=snapshot["mu_summary"],
320+            J_summary=snapshot["J_summary"],
321+        ),
322+        _check(
323+            "feedback_and_output_fields_are_populated",
324+            snapshot["output_mode"] in OUTPUT_MODES
325+            and bool(snapshot["feedback_effect"].get("applied_tokens")),
326+            "snapshot_state exposes output_mode and feedback_effect with applied feedback evidence.",
327+            output_mode=snapshot["output_mode"],
328+            feedback_effect=snapshot["feedback_effect"],
329+        ),
330+        _check(
331+            "locked_homing_and_sedimentation_fields_are_populated",
332+            snapshot["bound_ability_core"] is not None
333+            and snapshot["anchor_pull"] > 0.0
334+            and snapshot["skill_belt_candidates"]
335+            and snapshot["sedimentation_trace"],
336+            "The locked homing and sedimentation-facing snapshot fields are populated under a controlled scenario.",
337+            bound_ability_core=snapshot["bound_ability_core"],
338+            anchor_pull=snapshot["anchor_pull"],
339+            drift_score=snapshot["drift_score"],
340+        ),
341+    ]
342+    return _section(
343+        checks,
344+        output_mode=snapshot["output_mode"],
345+        free_capacity=snapshot["free_capacity"],
346+    )
347+
348+
349+def _known_limitations() -> List[str]:
350+    return [
351+        "The validation harness is scenario-based and compact; it is not a benchmark or long-run stability suite.",
352+        "Checks focus on the locked observable runtime surface rather than richer semantic task performance.",
353+        "Sedimentation and homing remain explicit but heuristic, which is acceptable for the review/comparison stage.",
354+    ]
355+
356+
357+def _runtime_summary(report: Dict[str, Any], json_path: Path, markdown_path: Path) -> Dict[str, Any]:
358+    section_statuses = _section_statuses(report)
359+    return {
360+        "status": "pass" if all(status == "pass" for status in section_statuses) else "fail",
361+        "scenarios": ["interface", "smoke", "dynamics", "sedimentation", "snapshot"],
362+        "output_modes_observed": list(OUTPUT_MODES),
363+        "reports_generated": {
364+            "json": str(json_path),
365+            "markdown": str(markdown_path),
366+        },
367+        "ready_for_review": all(status == "pass" for status in section_statuses),
368+    }
369+
370+
371+def _overall_status(report: Dict[str, Any]) -> Dict[str, Any]:
372+    section_statuses = _section_statuses(report)
373+    passed_sections = sum(1 for status in section_statuses if status == "pass")
374+    failed_sections = len(section_statuses) - passed_sections
375+    ready = failed_sections == 0
376+    return {
377+        "status": "pass" if ready else "fail",
378+        "passed_sections": passed_sections,
379+        "failed_sections": failed_sections,
380+        "ready_for_review": ready,
381+        "summary": (
382+            "Branch A validation passed and is ready for review/comparison."
383+            if ready
384+            else "Branch A validation found issues that should be reviewed before comparison."
385+        ),
386+    }
387+
388+
389+def _render_markdown(report: Dict[str, Any]) -> str:
390+    lines = [
391+        "# Branch A Validation Report",
392+        "",
393+        f"- Branch: `{report['branch']}`",
394+        f"- Base commit: `{report['base_commit']}`",
395+        f"- Overall status: `{report['overall_status']['status'].upper()}`",
396+        f"- Ready for review/comparison: `{report['overall_status']['ready_for_review']}`",
397+        "",
398+        "## Runtime Summary",
399+        f"- Status: `{report['runtime_summary']['status'].upper()}`",
400+        f"- Scenarios: {', '.join(report['runtime_summary']['scenarios'])}",
401+        f"- Output modes observed: {', '.join(report['runtime_summary']['output_modes_observed'])}",
402+        "",
403+    ]
404+    for key, title in (
405+        ("interface_checks", "Interface Checks"),
406+        ("smoke_checks", "Smoke Checks"),
407+        ("dynamics_checks", "Dynamics Checks"),
408+        ("sedimentation_checks", "Sedimentation Checks"),
409+        ("snapshot_checks", "Snapshot Checks"),
410+    ):
411+        section = report[key]
412+        lines.extend(
413+            [
414+                f"## {title}",
415+                f"- Status: `{section['status'].upper()}`",
416+                f"- Passed: `{section['passed']}`",
417+                f"- Failed: `{section['failed']}`",
418+            ]
419+        )
420+        for check in section["checks"]:
421+            lines.append(f"- `{check['status'].upper()}` {check['name']}: {check['detail']}")
422+        lines.append("")
423+    lines.extend(
424+        [
425+            "## Known Limitations",
426+            *[f"- {item}" for item in report["known_limitations"]],
427+            "",
428+            "## Readiness",
429+            f"- {report['overall_status']['summary']}",
430+            "",
431+        ]
432+    )
433+    return "\n".join(lines)
434+
435+
436+def generate_validation_report(
437+    json_path: Path | str = DEFAULT_JSON_REPORT_PATH,
438+    markdown_path: Path | str = DEFAULT_MARKDOWN_REPORT_PATH,
439+) -> Dict[str, Any]:
440+    json_path = Path(json_path)
441+    markdown_path = Path(markdown_path)
442+    report: Dict[str, Any] = {
443+        "branch": _current_branch(),
444+        "base_commit": BASE_COMMIT,
445+        "runtime_summary": {},
446+        "interface_checks": _interface_checks(),
447+        "smoke_checks": _smoke_checks(),
448+        "dynamics_checks": _dynamics_checks(),
449+        "sedimentation_checks": _sedimentation_checks(),
450+        "snapshot_checks": _snapshot_checks(),
451+        "known_limitations": _known_limitations(),
452+        "overall_status": {},
453+    }
454+    report["runtime_summary"] = _runtime_summary(report, json_path, markdown_path)
455+    report["overall_status"] = _overall_status(report)
456+    json_path.parent.mkdir(parents=True, exist_ok=True)
457+    markdown_path.parent.mkdir(parents=True, exist_ok=True)
458+    json_path.write_text(json.dumps(report, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
459+    markdown_path.write_text(_render_markdown(report), encoding="utf-8")
460+    return report
461+
462+
463+def _parse_args(argv: Sequence[str]) -> argparse.Namespace:
464+    parser = argparse.ArgumentParser(description="Branch A validation and report generation.")
465+    parser.add_argument("--json-out", default=str(DEFAULT_JSON_REPORT_PATH))
466+    parser.add_argument("--markdown-out", default=str(DEFAULT_MARKDOWN_REPORT_PATH))
467+    return parser.parse_args(argv)
468+
469+
470+def main(argv: Sequence[str] | None = None) -> int:
471+    args = _parse_args(sys.argv[1:] if argv is None else argv)
472+    report = generate_validation_report(args.json_out, args.markdown_out)
473+    return 0 if report["overall_status"]["status"] == "pass" else 1
474+
475+
476+if __name__ == "__main__":
477+    raise SystemExit(main())
M plans/2026-03-31_branch_a_plan.md
+2, -2
1@@ -19,5 +19,5 @@ Branch A is the pure graph-native minimal runtime line:
2    Runtime dynamics, output feedback, observable decay, and mode degradation are now implemented and validated.
3 3. **Task 03: sedimentation path + skill belt candidates + merge/decay events** `[completed]`
4    Explicit sedimentation profiles, stage transitions, merge events, and decay-linked demotion are now implemented and validated.
5-4. **Task 04: unified validation/reporting against locked spec**
6-   Produce the comparable validation and reporting layer required by the locked docs.
7+4. **Task 04: unified validation/reporting against locked spec** `[completed]`
8+   Comparable JSON/Markdown validation reports and the review-ready acceptance harness are now in place.
A reports/2026-03-31_task04_branch_a_validation.json
+1307, -0
   1@@ -0,0 +1,1307 @@
   2+{
   3+  "branch": "branch-a/task04-validation-reporting",
   4+  "base_commit": "164c06af63812c69cab32d8f8a6c770b96f38ef6",
   5+  "runtime_summary": {
   6+    "status": "pass",
   7+    "scenarios": [
   8+      "interface",
   9+      "smoke",
  10+      "dynamics",
  11+      "sedimentation",
  12+      "snapshot"
  13+    ],
  14+    "output_modes_observed": [
  15+      "full",
  16+      "degraded",
  17+      "minimal"
  18+    ],
  19+    "reports_generated": {
  20+      "json": "/Users/george/code/CIE-Unified/reports/2026-03-31_task04_branch_a_validation.json",
  21+      "markdown": "/Users/george/code/CIE-Unified/reports/2026-03-31_task04_branch_a_validation.md"
  22+    },
  23+    "ready_for_review": true
  24+  },
  25+  "interface_checks": {
  26+    "status": "pass",
  27+    "passed": 2,
  28+    "failed": 0,
  29+    "checks": [
  30+      {
  31+        "name": "runtime_initializes",
  32+        "status": "pass",
  33+        "detail": "A fresh runtime exposes minimal output mode and no active region.",
  34+        "observed_output_mode": "minimal",
  35+        "observed_active_region": []
  36+      },
  37+      {
  38+        "name": "locked_interface_presence",
  39+        "status": "pass",
  40+        "detail": "Branch A exposes the locked runtime interface required by the spec.",
  41+        "interface": [
  42+          "ingest",
  43+          "step",
  44+          "emit",
  45+          "commit_feedback",
  46+          "snapshot_state",
  47+          "reset_session"
  48+        ]
  49+      }
  50+    ],
  51+    "summary": {
  52+      "snapshot_keys": [
  53+        "J_summary",
  54+        "active_region",
  55+        "anchor_pull",
  56+        "bound_ability_core",
  57+        "decay_events",
  58+        "drift_score",
  59+        "experience_regions",
  60+        "feedback_effect",
  61+        "free_capacity",
  62+        "merge_events",
  63+        "mu_summary",
  64+        "output_mode",
  65+        "phi_summary",
  66+        "sedimentation_trace",
  67+        "skill_belt_candidates"
  68+      ],
  69+      "required_snapshot_keys": [
  70+        "J_summary",
  71+        "active_region",
  72+        "anchor_pull",
  73+        "bound_ability_core",
  74+        "decay_events",
  75+        "drift_score",
  76+        "experience_regions",
  77+        "feedback_effect",
  78+        "free_capacity",
  79+        "merge_events",
  80+        "mu_summary",
  81+        "output_mode",
  82+        "phi_summary",
  83+        "sedimentation_trace",
  84+        "skill_belt_candidates"
  85+      ]
  86+    }
  87+  },
  88+  "smoke_checks": {
  89+    "status": "pass",
  90+    "passed": 4,
  91+    "failed": 0,
  92+    "checks": [
  93+      {
  94+        "name": "ingest_queues_signal",
  95+        "status": "pass",
  96+        "detail": "Ingest stores external tokens and anchor hints before the step loop runs.",
  97+        "queued_tokens": [
  98+          "graph",
  99+          "native",
 100+          "observability"
 101+        ],
 102+        "queued_anchors": [
 103+          "anchor"
 104+        ]
 105+      },
 106+      {
 107+        "name": "step_materializes_graph_state",
 108+        "status": "pass",
 109+        "detail": "Stepping from a queued input creates observable phi/mu/J state.",
 110+        "phi_summary": {
 111+          "node_count": 8,
 112+          "total_potential": 0.9771,
 113+          "top_nodes": [
 114+            {
 115+              "node": "native",
 116+              "value": 0.3148
 117+            },
 118+            {
 119+              "node": "observability",
 120+              "value": 0.2646
 121+            },
 122+            {
 123+              "node": "graph",
 124+              "value": 0.2556
 125+            },
 126+            {
 127+              "node": "anchor",
 128+              "value": 0.1245
 129+            },
 130+            {
 131+              "node": "smoke",
 132+              "value": 0.0165
 133+            }
 134+          ]
 135+        },
 136+        "mu_summary": {
 137+          "active_count": 4,
 138+          "total_activation": 1.2823,
 139+          "top_nodes": [
 140+            {
 141+              "node": "native",
 142+              "value": 0.6435
 143+            },
 144+            {
 145+              "node": "observability",
 146+              "value": 0.2917
 147+            },
 148+            {
 149+              "node": "graph",
 150+              "value": 0.2746
 151+            },
 152+            {
 153+              "node": "smoke",
 154+              "value": 0.0725
 155+            }
 156+          ]
 157+        },
 158+        "J_summary": {
 159+          "edge_count": 15,
 160+          "total_flow": 1.7255,
 161+          "top_flows": [
 162+            {
 163+              "edge": "native->observability",
 164+              "flow": 0.2709
 165+            },
 166+            {
 167+              "edge": "graph->native",
 168+              "flow": 0.2703
 169+            },
 170+            {
 171+              "edge": "smoke->graph",
 172+              "flow": 0.2143
 173+            },
 174+            {
 175+              "edge": "a->validation",
 176+              "flow": 0.2053
 177+            },
 178+            {
 179+              "edge": "anchor->branch",
 180+              "flow": 0.2053
 181+            }
 182+          ]
 183+        }
 184+      },
 185+      {
 186+        "name": "emit_queues_output_feedback",
 187+        "status": "pass",
 188+        "detail": "Emit creates a real output-to-input feedback signal rather than a log-only artifact.",
 189+        "emitted_output": "degraded: native / observability",
 190+        "feedback_effect": {
 191+          "source": "emit",
 192+          "mode": "degraded",
 193+          "queued_tokens": [
 194+            "native",
 195+            "observability"
 196+          ],
 197+          "queued_strength": 0.38,
 198+          "confidence_proxy": 0.4379,
 199+          "queued_step": 2,
 200+          "last_applied_step": null
 201+        }
 202+      },
 203+      {
 204+        "name": "feedback_changes_later_state",
 205+        "status": "pass",
 206+        "detail": "Queued feedback is applied on the next step and changes later runtime state.",
 207+        "feedback_effect": {
 208+          "source": "emit",
 209+          "mode": "degraded",
 210+          "queued_tokens": [
 211+            "native",
 212+            "observability"
 213+          ],
 214+          "queued_strength": 0.38,
 215+          "confidence_proxy": 0.4379,
 216+          "queued_step": 2,
 217+          "last_applied_step": 3,
 218+          "applied_tokens": [
 219+            "native",
 220+            "observability",
 221+            "graph"
 222+          ],
 223+          "phi_delta": 0.0401,
 224+          "mu_delta": 0.0552,
 225+          "flow_delta": 0.0492,
 226+          "stage_after": {
 227+            "native": "skill_belt",
 228+            "observability": "skill_belt",
 229+            "graph": "experience"
 230+          },
 231+          "bound_ability_core": "native"
 232+        }
 233+      }
 234+    ],
 235+    "summary": {
 236+      "emitted_output": "degraded: native / observability",
 237+      "bound_ability_core": "native",
 238+      "active_region": [
 239+        "native",
 240+        "observability",
 241+        "graph",
 242+        "smoke"
 243+      ]
 244+    }
 245+  },
 246+  "dynamics_checks": {
 247+    "status": "pass",
 248+    "passed": 4,
 249+    "failed": 0,
 250+    "checks": [
 251+      {
 252+        "name": "phi_mu_J_are_observable",
 253+        "status": "pass",
 254+        "detail": "Multi-step dynamics produce visible changes across phi, mu, and J.",
 255+        "step_one": {
 256+          "phi_summary": {
 257+            "node_count": 4,
 258+            "total_potential": 0.8956,
 259+            "top_nodes": [
 260+              {
 261+                "node": "alpha",
 262+                "value": 0.4895
 263+              },
 264+              {
 265+                "node": "beta",
 266+                "value": 0.2604
 267+              },
 268+              {
 269+                "node": "anchor",
 270+                "value": 0.1219
 271+              },
 272+              {
 273+                "node": "gamma",
 274+                "value": 0.0238
 275+              }
 276+            ]
 277+          },
 278+          "mu_summary": {
 279+            "active_count": 3,
 280+            "total_activation": 1.7591,
 281+            "top_nodes": [
 282+              {
 283+                "node": "alpha",
 284+                "value": 1.0548
 285+              },
 286+              {
 287+                "node": "beta",
 288+                "value": 0.5423
 289+              },
 290+              {
 291+                "node": "gamma",
 292+                "value": 0.162
 293+              }
 294+            ]
 295+          },
 296+          "J_summary": {
 297+            "edge_count": 7,
 298+            "total_flow": 0.828,
 299+            "top_flows": [
 300+              {
 301+                "edge": "alpha->beta",
 302+                "flow": 0.2788
 303+              },
 304+              {
 305+                "edge": "gamma->alpha",
 306+                "flow": 0.2351
 307+              },
 308+              {
 309+                "edge": "anchor->gamma",
 310+                "flow": 0.2256
 311+              },
 312+              {
 313+                "edge": "beta->alpha",
 314+                "flow": 0.0486
 315+              },
 316+              {
 317+                "edge": "alpha->gamma",
 318+                "flow": 0.0357
 319+              }
 320+            ]
 321+          },
 322+          "active_region": [
 323+            "alpha",
 324+            "beta",
 325+            "gamma"
 326+          ],
 327+          "bound_ability_core": "alpha",
 328+          "anchor_pull": 0.0,
 329+          "drift_score": 0.2401,
 330+          "free_capacity": 0.7801,
 331+          "experience_regions": [
 332+            {
 333+              "region": "alpha",
 334+              "nodes": [
 335+                "alpha",
 336+                "anchor",
 337+                "beta",
 338+                "gamma"
 339+              ],
 340+              "stage": "experience",
 341+              "activation": 1.7591,
 342+              "potential": 0.8956,
 343+              "candidate_score": 3.2778,
 344+              "stable_steps": 1
 345+            }
 346+          ],
 347+          "skill_belt_candidates": [
 348+            {
 349+              "node": "alpha",
 350+              "score": 1.1178,
 351+              "stage": "experience",
 352+              "flow": 0.5983,
 353+              "stable_steps": 1,
 354+              "touches": 2,
 355+              "target_core": "alpha"
 356+            },
 357+            {
 358+              "node": "beta",
 359+              "score": 0.8617,
 360+              "stage": "experience",
 361+              "flow": 0.3274,
 362+              "stable_steps": 1,
 363+              "touches": 2,
 364+              "target_core": "alpha"
 365+            },
 366+            {
 367+              "node": "gamma",
 368+              "score": 0.7247,
 369+              "stage": "experience",
 370+              "flow": 0.4974,
 371+              "stable_steps": 1,
 372+              "touches": 2,
 373+              "target_core": "alpha"
 374+            },
 375+            {
 376+              "node": "anchor",
 377+              "score": 0.5736,
 378+              "stage": "experience",
 379+              "flow": 0.2265,
 380+              "stable_steps": 1,
 381+              "touches": 1,
 382+              "target_core": "alpha"
 383+            }
 384+          ],
 385+          "sedimentation_trace": [
 386+            {
 387+              "step": 1,
 388+              "node": "alpha",
 389+              "direction": "promote",
 390+              "from": "memory",
 391+              "to": "experience",
 392+              "touches": 2,
 393+              "stable_steps": 1,
 394+              "dormant_steps": 0,
 395+              "candidate_score": 1.1178,
 396+              "resonance": 1.3338,
 397+              "flow": 0.5983
 398+            },
 399+            {
 400+              "step": 1,
 401+              "node": "anchor",
 402+              "direction": "promote",
 403+              "from": "memory",
 404+              "to": "experience",
 405+              "touches": 1,
 406+              "stable_steps": 1,
 407+              "dormant_steps": 0,
 408+              "candidate_score": 0.5736,
 409+              "resonance": 0.0834,
 410+              "flow": 0.2265
 411+            },
 412+            {
 413+              "step": 1,
 414+              "node": "beta",
 415+              "direction": "promote",
 416+              "from": "memory",
 417+              "to": "experience",
 418+              "touches": 2,
 419+              "stable_steps": 1,
 420+              "dormant_steps": 0,
 421+              "candidate_score": 0.8617,
 422+              "resonance": 0.6924,
 423+              "flow": 0.3274
 424+            },
 425+            {
 426+              "step": 1,
 427+              "node": "gamma",
 428+              "direction": "promote",
 429+              "from": "memory",
 430+              "to": "experience",
 431+              "touches": 2,
 432+              "stable_steps": 1,
 433+              "dormant_steps": 0,
 434+              "candidate_score": 0.7247,
 435+              "resonance": 0.2598,
 436+              "flow": 0.4974
 437+            }
 438+          ],
 439+          "merge_events": [],
 440+          "decay_events": [
 441+            {
 442+              "step": 1,
 443+              "kind": "phi_decay",
 444+              "target": "alpha",
 445+              "amount": 0.0151,
 446+              "age": 0
 447+            },
 448+            {
 449+              "step": 1,
 450+              "kind": "mu_decay",
 451+              "target": "alpha",
 452+              "amount": 0.0794,
 453+              "age": 0
 454+            },
 455+            {
 456+              "step": 1,
 457+              "kind": "mu_decay",
 458+              "target": "gamma",
 459+              "amount": 0.0221,
 460+              "age": 0
 461+            },
 462+            {
 463+              "step": 1,
 464+              "kind": "mu_decay",
 465+              "target": "beta",
 466+              "amount": 0.0739,
 467+              "age": 0
 468+            },
 469+            {
 470+              "step": 1,
 471+              "kind": "mu_prune",
 472+              "target": "anchor",
 473+              "amount": 0.0283,
 474+              "age": 0
 475+            },
 476+            {
 477+              "step": 1,
 478+              "kind": "J_decay",
 479+              "target": "anchor->gamma",
 480+              "amount": 0.0144,
 481+              "age": 0
 482+            }
 483+          ],
 484+          "output_mode": "degraded",
 485+          "feedback_effect": {}
 486+        },
 487+        "step_three": {
 488+          "phi_summary": {
 489+            "node_count": 4,
 490+            "total_potential": 1.1043,
 491+            "top_nodes": [
 492+              {
 493+                "node": "alpha",
 494+                "value": 0.548
 495+              },
 496+              {
 497+                "node": "beta",
 498+                "value": 0.3074
 499+              },
 500+              {
 501+                "node": "anchor",
 502+                "value": 0.1883
 503+              },
 504+              {
 505+                "node": "gamma",
 506+                "value": 0.0606
 507+              }
 508+            ]
 509+          },
 510+          "mu_summary": {
 511+            "active_count": 3,
 512+            "total_activation": 0.9573,
 513+            "top_nodes": [
 514+              {
 515+                "node": "alpha",
 516+                "value": 0.5346
 517+              },
 518+              {
 519+                "node": "beta",
 520+                "value": 0.2701
 521+              },
 522+              {
 523+                "node": "gamma",
 524+                "value": 0.1526
 525+              }
 526+            ]
 527+          },
 528+          "J_summary": {
 529+            "edge_count": 9,
 530+            "total_flow": 0.9228,
 531+            "top_flows": [
 532+              {
 533+                "edge": "alpha->beta",
 534+                "flow": 0.3151
 535+              },
 536+              {
 537+                "edge": "gamma->alpha",
 538+                "flow": 0.2352
 539+              },
 540+              {
 541+                "edge": "anchor->gamma",
 542+                "flow": 0.1807
 543+              },
 544+              {
 545+                "edge": "beta->alpha",
 546+                "flow": 0.1007
 547+              },
 548+              {
 549+                "edge": "alpha->gamma",
 550+                "flow": 0.0737
 551+              }
 552+            ]
 553+          },
 554+          "active_region": [
 555+            "alpha",
 556+            "beta",
 557+            "gamma"
 558+          ],
 559+          "bound_ability_core": "alpha",
 560+          "anchor_pull": 0.0001,
 561+          "drift_score": 0.2462,
 562+          "free_capacity": 0.8803,
 563+          "experience_regions": [
 564+            {
 565+              "region": "alpha",
 566+              "nodes": [
 567+                "alpha",
 568+                "anchor",
 569+                "beta",
 570+                "gamma"
 571+              ],
 572+              "stage": "skill_belt",
 573+              "activation": 0.9573,
 574+              "potential": 1.1043,
 575+              "candidate_score": 7.4796,
 576+              "stable_steps": 3
 577+            }
 578+          ],
 579+          "skill_belt_candidates": [
 580+            {
 581+              "node": "alpha",
 582+              "score": 2.4896,
 583+              "stage": "skill_belt",
 584+              "flow": 0.7247,
 585+              "stable_steps": 3,
 586+              "touches": 4,
 587+              "target_core": "alpha"
 588+            },
 589+            {
 590+              "node": "beta",
 591+              "score": 2.0219,
 592+              "stage": "skill_belt",
 593+              "flow": 0.4158,
 594+              "stable_steps": 3,
 595+              "touches": 4,
 596+              "target_core": "alpha"
 597+            },
 598+            {
 599+              "node": "gamma",
 600+              "score": 1.6643,
 601+              "stage": "skill_belt",
 602+              "flow": 0.5001,
 603+              "stable_steps": 3,
 604+              "touches": 3,
 605+              "target_core": "alpha"
 606+            },
 607+            {
 608+              "node": "anchor",
 609+              "score": 1.3038,
 610+              "stage": "experience",
 611+              "flow": 0.1912,
 612+              "stable_steps": 3,
 613+              "touches": 1,
 614+              "target_core": "alpha"
 615+            }
 616+          ],
 617+          "sedimentation_trace": [
 618+            {
 619+              "step": 1,
 620+              "node": "alpha",
 621+              "direction": "promote",
 622+              "from": "memory",
 623+              "to": "experience",
 624+              "touches": 2,
 625+              "stable_steps": 1,
 626+              "dormant_steps": 0,
 627+              "candidate_score": 1.1178,
 628+              "resonance": 1.3338,
 629+              "flow": 0.5983
 630+            },
 631+            {
 632+              "step": 1,
 633+              "node": "anchor",
 634+              "direction": "promote",
 635+              "from": "memory",
 636+              "to": "experience",
 637+              "touches": 1,
 638+              "stable_steps": 1,
 639+              "dormant_steps": 0,
 640+              "candidate_score": 0.5736,
 641+              "resonance": 0.0834,
 642+              "flow": 0.2265
 643+            },
 644+            {
 645+              "step": 1,
 646+              "node": "beta",
 647+              "direction": "promote",
 648+              "from": "memory",
 649+              "to": "experience",
 650+              "touches": 2,
 651+              "stable_steps": 1,
 652+              "dormant_steps": 0,
 653+              "candidate_score": 0.8617,
 654+              "resonance": 0.6924,
 655+              "flow": 0.3274
 656+            },
 657+            {
 658+              "step": 1,
 659+              "node": "gamma",
 660+              "direction": "promote",
 661+              "from": "memory",
 662+              "to": "experience",
 663+              "touches": 2,
 664+              "stable_steps": 1,
 665+              "dormant_steps": 0,
 666+              "candidate_score": 0.7247,
 667+              "resonance": 0.2598,
 668+              "flow": 0.4974
 669+            },
 670+            {
 671+              "step": 2,
 672+              "node": "alpha",
 673+              "direction": "promote",
 674+              "from": "experience",
 675+              "to": "skill_belt",
 676+              "touches": 3,
 677+              "stable_steps": 2,
 678+              "dormant_steps": 0,
 679+              "candidate_score": 1.8591,
 680+              "resonance": 2.175,
 681+              "flow": 0.6764
 682+            },
 683+            {
 684+              "step": 2,
 685+              "node": "beta",
 686+              "direction": "promote",
 687+              "from": "experience",
 688+              "to": "skill_belt",
 689+              "touches": 3,
 690+              "stable_steps": 2,
 691+              "dormant_steps": 0,
 692+              "candidate_score": 1.4692,
 693+              "resonance": 1.1255,
 694+              "flow": 0.3827
 695+            },
 696+            {
 697+              "step": 3,
 698+              "node": "gamma",
 699+              "direction": "promote",
 700+              "from": "experience",
 701+              "to": "skill_belt",
 702+              "touches": 3,
 703+              "stable_steps": 3,
 704+              "dormant_steps": 0,
 705+              "candidate_score": 1.6643,
 706+              "resonance": 0.6859,
 707+              "flow": 0.5001
 708+            }
 709+          ],
 710+          "merge_events": [],
 711+          "decay_events": [
 712+            {
 713+              "step": 1,
 714+              "kind": "phi_decay",
 715+              "target": "alpha",
 716+              "amount": 0.0151,
 717+              "age": 0
 718+            },
 719+            {
 720+              "step": 1,
 721+              "kind": "mu_decay",
 722+              "target": "alpha",
 723+              "amount": 0.0794,
 724+              "age": 0
 725+            },
 726+            {
 727+              "step": 1,
 728+              "kind": "mu_decay",
 729+              "target": "gamma",
 730+              "amount": 0.0221,
 731+              "age": 0
 732+            },
 733+            {
 734+              "step": 1,
 735+              "kind": "mu_decay",
 736+              "target": "beta",
 737+              "amount": 0.0739,
 738+              "age": 0
 739+            },
 740+            {
 741+              "step": 1,
 742+              "kind": "mu_prune",
 743+              "target": "anchor",
 744+              "amount": 0.0283,
 745+              "age": 0
 746+            },
 747+            {
 748+              "step": 1,
 749+              "kind": "J_decay",
 750+              "target": "anchor->gamma",
 751+              "amount": 0.0144,
 752+              "age": 0
 753+            },
 754+            {
 755+              "step": 2,
 756+              "kind": "phi_decay",
 757+              "target": "alpha",
 758+              "amount": 0.0162,
 759+              "age": 0
 760+            },
 761+            {
 762+              "step": 2,
 763+              "kind": "mu_decay",
 764+              "target": "alpha",
 765+              "amount": 0.0564,
 766+              "age": 0
 767+            },
 768+            {
 769+              "step": 2,
 770+              "kind": "mu_decay",
 771+              "target": "gamma",
 772+              "amount": 0.0242,
 773+              "age": 0
 774+            },
 775+            {
 776+              "step": 2,
 777+              "kind": "mu_decay",
 778+              "target": "beta",
 779+              "amount": 0.051,
 780+              "age": 0
 781+            },
 782+            {
 783+              "step": 2,
 784+              "kind": "mu_prune",
 785+              "target": "anchor",
 786+              "amount": 0.0188,
 787+              "age": 0
 788+            },
 789+            {
 790+              "step": 2,
 791+              "kind": "J_decay",
 792+              "target": "anchor->gamma",
 793+              "amount": 0.0203,
 794+              "age": 1
 795+            },
 796+            {
 797+              "step": 3,
 798+              "kind": "phi_decay",
 799+              "target": "alpha",
 800+              "amount": 0.0169,
 801+              "age": 0
 802+            },
 803+            {
 804+              "step": 3,
 805+              "kind": "mu_decay",
 806+              "target": "alpha",
 807+              "amount": 0.0402,
 808+              "age": 0
 809+            },
 810+            {
 811+              "step": 3,
 812+              "kind": "mu_decay",
 813+              "target": "gamma",
 814+              "amount": 0.0208,
 815+              "age": 0
 816+            },
 817+            {
 818+              "step": 3,
 819+              "kind": "mu_decay",
 820+              "target": "beta",
 821+              "amount": 0.0368,
 822+              "age": 0
 823+            },
 824+            {
 825+              "step": 3,
 826+              "kind": "mu_prune",
 827+              "target": "anchor",
 828+              "amount": 0.0207,
 829+              "age": 0
 830+            },
 831+            {
 832+              "step": 3,
 833+              "kind": "J_decay",
 834+              "target": "anchor->gamma",
 835+              "amount": 0.0246,
 836+              "age": 2
 837+            }
 838+          ],
 839+          "output_mode": "degraded",
 840+          "feedback_effect": {}
 841+        }
 842+      },
 843+      {
 844+        "name": "homing_signals_are_populated",
 845+        "status": "pass",
 846+        "detail": "The runtime exposes bound_ability_core, anchor_pull, and drift_score as active homing signals.",
 847+        "bound_ability_core": "alpha",
 848+        "anchor_pull": 0.0001,
 849+        "drift_score": 0.2462
 850+      },
 851+      {
 852+        "name": "decay_and_forgetting_are_visible",
 853+        "status": "pass",
 854+        "detail": "Decay/forgetting remains real and observable through decay events and stage demotion.",
 855+        "decay_events": [
 856+          {
 857+            "step": 10,
 858+            "kind": "J_decay",
 859+            "target": "anchor->stale",
 860+            "amount": 0.0905,
 861+            "age": 6
 862+          },
 863+          {
 864+            "step": 10,
 865+            "kind": "J_decay",
 866+            "target": "stale->alpha",
 867+            "amount": 0.1172,
 868+            "age": 6
 869+          },
 870+          {
 871+            "step": 10,
 872+            "kind": "J_decay",
 873+            "target": "alpha->beta",
 874+            "amount": 0.1203,
 875+            "age": 6
 876+          },
 877+          {
 878+            "step": 10,
 879+            "kind": "J_decay",
 880+            "target": "stale->anchor",
 881+            "amount": 0.0137,
 882+            "age": 6
 883+          },
 884+          {
 885+            "step": 10,
 886+            "kind": "J_decay",
 887+            "target": "alpha->stale",
 888+            "amount": 0.0181,
 889+            "age": 6
 890+          },
 891+          {
 892+            "step": 10,
 893+            "kind": "J_decay",
 894+            "target": "beta->alpha",
 895+            "amount": 0.0331,
 896+            "age": 6
 897+          }
 898+        ]
 899+      },
 900+      {
 901+        "name": "degraded_output_modes_exist",
 902+        "status": "pass",
 903+        "detail": "The runtime emits full, degraded, and minimal outputs under different runtime conditions.",
 904+        "observed_modes": [
 905+          "degraded",
 906+          "full",
 907+          "minimal"
 908+        ],
 909+        "outputs": {
 910+          "full": "full: focus -> anchor",
 911+          "degraded": "degraded: delta / gamma",
 912+          "minimal": "minimal: delta"
 913+        }
 914+      }
 915+    ],
 916+    "summary": {
 917+      "observed_modes": [
 918+        "degraded",
 919+        "full",
 920+        "minimal"
 921+      ],
 922+      "total_decay_events": 24
 923+    }
 924+  },
 925+  "sedimentation_checks": {
 926+    "status": "pass",
 927+    "passed": 4,
 928+    "failed": 0,
 929+    "checks": [
 930+      {
 931+        "name": "sedimentation_trace_exists",
 932+        "status": "pass",
 933+        "detail": "Sedimentation history is exported as explicit runtime trace entries.",
 934+        "trace_tail": [
 935+          {
 936+            "step": 2,
 937+            "node": "anchor",
 938+            "direction": "promote",
 939+            "from": "experience",
 940+            "to": "skill_belt",
 941+            "touches": 4,
 942+            "stable_steps": 2,
 943+            "dormant_steps": 0,
 944+            "candidate_score": 1.619,
 945+            "resonance": 0.9619,
 946+            "flow": 0.5802
 947+          },
 948+          {
 949+            "step": 2,
 950+            "node": "beta",
 951+            "direction": "promote",
 952+            "from": "experience",
 953+            "to": "skill_belt",
 954+            "touches": 4,
 955+            "stable_steps": 2,
 956+            "dormant_steps": 0,
 957+            "candidate_score": 1.8566,
 958+            "resonance": 1.7754,
 959+            "flow": 0.7005
 960+          },
 961+          {
 962+            "step": 4,
 963+            "node": "alpha",
 964+            "direction": "promote",
 965+            "from": "skill_belt",
 966+            "to": "ability_core",
 967+            "touches": 8,
 968+            "stable_steps": 4,
 969+            "dormant_steps": 0,
 970+            "candidate_score": 3.7893,
 971+            "resonance": 6.0,
 972+            "flow": 2.7831
 973+          },
 974+          {
 975+            "step": 4,
 976+            "node": "anchor",
 977+            "direction": "promote",
 978+            "from": "skill_belt",
 979+            "to": "ability_core",
 980+            "touches": 8,
 981+            "stable_steps": 4,
 982+            "dormant_steps": 0,
 983+            "candidate_score": 3.5277,
 984+            "resonance": 2.926,
 985+            "flow": 1.2539
 986+          },
 987+          {
 988+            "step": 4,
 989+            "node": "beta",
 990+            "direction": "promote",
 991+            "from": "skill_belt",
 992+            "to": "ability_core",
 993+            "touches": 8,
 994+            "stable_steps": 4,
 995+            "dormant_steps": 0,
 996+            "candidate_score": 3.5592,
 997+            "resonance": 4.7325,
 998+            "flow": 1.5292
 999+          }
1000+        ]
1001+      },
1002+      {
1003+        "name": "stage_progression_matches_locked_path",
1004+        "status": "pass",
1005+        "detail": "Repeated activation follows the locked memory -> experience -> skill_belt -> ability_core path.",
1006+        "alpha_trace": [
1007+          {
1008+            "step": 1,
1009+            "node": "alpha",
1010+            "direction": "promote",
1011+            "from": "memory",
1012+            "to": "experience",
1013+            "touches": 2,
1014+            "stable_steps": 1,
1015+            "dormant_steps": 0,
1016+            "candidate_score": 1.1178,
1017+            "resonance": 1.3338,
1018+            "flow": 0.5983
1019+          },
1020+          {
1021+            "step": 2,
1022+            "node": "alpha",
1023+            "direction": "promote",
1024+            "from": "experience",
1025+            "to": "skill_belt",
1026+            "touches": 4,
1027+            "stable_steps": 2,
1028+            "dormant_steps": 0,
1029+            "candidate_score": 2.3563,
1030+            "resonance": 3.546,
1031+            "flow": 1.2806
1032+          },
1033+          {
1034+            "step": 4,
1035+            "node": "alpha",
1036+            "direction": "promote",
1037+            "from": "skill_belt",
1038+            "to": "ability_core",
1039+            "touches": 8,
1040+            "stable_steps": 4,
1041+            "dormant_steps": 0,
1042+            "candidate_score": 3.7893,
1043+            "resonance": 6.0,
1044+            "flow": 2.7831
1045+          }
1046+        ]
1047+      },
1048+      {
1049+        "name": "skill_belt_candidates_have_evidence",
1050+        "status": "pass",
1051+        "detail": "Skill-belt candidates are backed by repeated activation, stability, and flow evidence.",
1052+        "alpha_candidate": {
1053+          "node": "alpha",
1054+          "score": 3.7893,
1055+          "stage": "ability_core",
1056+          "flow": 2.7831,
1057+          "stable_steps": 4,
1058+          "touches": 8,
1059+          "target_core": "alpha"
1060+        }
1061+      },
1062+      {
1063+        "name": "merge_events_are_recorded",
1064+        "status": "pass",
1065+        "detail": "Stable skill-belt structures can produce explicit merge events into ability-core structures.",
1066+        "merge_events": [
1067+          {
1068+            "step": 4,
1069+            "event": "skill_belt_merge",
1070+            "node": "alpha",
1071+            "target_core": "alpha",
1072+            "support_nodes": [
1073+              "anchor",
1074+              "beta"
1075+            ],
1076+            "candidate_score": 3.7893,
1077+            "stable_steps": 4
1078+          },
1079+          {
1080+            "step": 4,
1081+            "event": "skill_belt_merge",
1082+            "node": "anchor",
1083+            "target_core": "alpha",
1084+            "support_nodes": [
1085+              "alpha"
1086+            ],
1087+            "candidate_score": 3.5277,
1088+            "stable_steps": 4
1089+          },
1090+          {
1091+            "step": 4,
1092+            "event": "skill_belt_merge",
1093+            "node": "beta",
1094+            "target_core": "alpha",
1095+            "support_nodes": [
1096+              "alpha"
1097+            ],
1098+            "candidate_score": 3.5592,
1099+            "stable_steps": 4
1100+          }
1101+        ]
1102+      }
1103+    ],
1104+    "summary": {
1105+      "experience_regions": [
1106+        {
1107+          "region": "alpha",
1108+          "nodes": [
1109+            "alpha",
1110+            "anchor",
1111+            "beta"
1112+          ],
1113+          "stage": "ability_core",
1114+          "activation": 5.0299,
1115+          "potential": 4.2739,
1116+          "candidate_score": 10.8762,
1117+          "stable_steps": 4
1118+        }
1119+      ],
1120+      "bound_ability_core": "alpha"
1121+    }
1122+  },
1123+  "snapshot_checks": {
1124+    "status": "pass",
1125+    "passed": 4,
1126+    "failed": 0,
1127+    "checks": [
1128+      {
1129+        "name": "locked_snapshot_fields_present",
1130+        "status": "pass",
1131+        "detail": "snapshot_state returns the locked comparable field set.",
1132+        "observed_keys": [
1133+          "J_summary",
1134+          "active_region",
1135+          "anchor_pull",
1136+          "bound_ability_core",
1137+          "decay_events",
1138+          "drift_score",
1139+          "experience_regions",
1140+          "feedback_effect",
1141+          "free_capacity",
1142+          "merge_events",
1143+          "mu_summary",
1144+          "output_mode",
1145+          "phi_summary",
1146+          "sedimentation_trace",
1147+          "skill_belt_candidates"
1148+        ],
1149+        "required_keys": [
1150+          "J_summary",
1151+          "active_region",
1152+          "anchor_pull",
1153+          "bound_ability_core",
1154+          "decay_events",
1155+          "drift_score",
1156+          "experience_regions",
1157+          "feedback_effect",
1158+          "free_capacity",
1159+          "merge_events",
1160+          "mu_summary",
1161+          "output_mode",
1162+          "phi_summary",
1163+          "sedimentation_trace",
1164+          "skill_belt_candidates"
1165+        ]
1166+      },
1167+      {
1168+        "name": "summary_fields_are_meaningful",
1169+        "status": "pass",
1170+        "detail": "phi_summary, mu_summary, and J_summary expose non-empty observable summaries after activity.",
1171+        "phi_summary": {
1172+          "node_count": 8,
1173+          "total_potential": 1.9781,
1174+          "top_nodes": [
1175+            {
1176+              "node": "native",
1177+              "value": 0.4828
1178+            },
1179+            {
1180+              "node": "graph",
1181+              "value": 0.417
1182+            },
1183+            {
1184+              "node": "a",
1185+              "value": 0.2996
1186+            },
1187+            {
1188+              "node": "feedback",
1189+              "value": 0.2814
1190+            },
1191+            {
1192+              "node": "branch",
1193+              "value": 0.2513
1194+            }
1195+          ]
1196+        },
1197+        "mu_summary": {
1198+          "active_count": 7,
1199+          "total_activation": 2.0156,
1200+          "top_nodes": [
1201+            {
1202+              "node": "native",
1203+              "value": 0.8468
1204+            },
1205+            {
1206+              "node": "graph",
1207+              "value": 0.4725
1208+            },
1209+            {
1210+              "node": "feedback",
1211+              "value": 0.2321
1212+            },
1213+            {
1214+              "node": "a",
1215+              "value": 0.2241
1216+            },
1217+            {
1218+              "node": "branch",
1219+              "value": 0.1324
1220+            }
1221+          ]
1222+        },
1223+        "J_summary": {
1224+          "edge_count": 26,
1225+          "total_flow": 2.2016,
1226+          "top_flows": [
1227+            {
1228+              "edge": "graph->native",
1229+              "flow": 0.2947
1230+            },
1231+            {
1232+              "edge": "native->feedback",
1233+              "flow": 0.2864
1234+            },
1235+            {
1236+              "edge": "a->graph",
1237+              "flow": 0.2406
1238+            },
1239+            {
1240+              "edge": "branch->a",
1241+              "flow": 0.2362
1242+            },
1243+            {
1244+              "edge": "state->branch",
1245+              "flow": 0.2034
1246+            }
1247+          ]
1248+        }
1249+      },
1250+      {
1251+        "name": "feedback_and_output_fields_are_populated",
1252+        "status": "pass",
1253+        "detail": "snapshot_state exposes output_mode and feedback_effect with applied feedback evidence.",
1254+        "output_mode": "degraded",
1255+        "feedback_effect": {
1256+          "source": "emit",
1257+          "mode": "degraded",
1258+          "queued_tokens": [
1259+            "native",
1260+            "graph"
1261+          ],
1262+          "queued_strength": 0.38,
1263+          "confidence_proxy": 0.3667,
1264+          "queued_step": 2,
1265+          "last_applied_step": 3,
1266+          "applied_tokens": [
1267+            "native",
1268+            "graph",
1269+            "a"
1270+          ],
1271+          "phi_delta": 0.0401,
1272+          "mu_delta": 0.0552,
1273+          "flow_delta": 0.0492,
1274+          "stage_after": {
1275+            "native": "skill_belt",
1276+            "graph": "skill_belt",
1277+            "a": "skill_belt"
1278+          },
1279+          "bound_ability_core": "native"
1280+        }
1281+      },
1282+      {
1283+        "name": "locked_homing_and_sedimentation_fields_are_populated",
1284+        "status": "pass",
1285+        "detail": "The locked homing and sedimentation-facing snapshot fields are populated under a controlled scenario.",
1286+        "bound_ability_core": "native",
1287+        "anchor_pull": 0.0484,
1288+        "drift_score": 0.6508
1289+      }
1290+    ],
1291+    "summary": {
1292+      "output_mode": "degraded",
1293+      "free_capacity": 0.7481
1294+    }
1295+  },
1296+  "known_limitations": [
1297+    "The validation harness is scenario-based and compact; it is not a benchmark or long-run stability suite.",
1298+    "Checks focus on the locked observable runtime surface rather than richer semantic task performance.",
1299+    "Sedimentation and homing remain explicit but heuristic, which is acceptable for the review/comparison stage."
1300+  ],
1301+  "overall_status": {
1302+    "status": "pass",
1303+    "passed_sections": 5,
1304+    "failed_sections": 0,
1305+    "ready_for_review": true,
1306+    "summary": "Branch A validation passed and is ready for review/comparison."
1307+  }
1308+}
A reports/2026-03-31_task04_branch_a_validation.md
+62, -0
 1@@ -0,0 +1,62 @@
 2+# Branch A Validation Report
 3+
 4+- Branch: `branch-a/task04-validation-reporting`
 5+- Base commit: `164c06af63812c69cab32d8f8a6c770b96f38ef6`
 6+- Overall status: `PASS`
 7+- Ready for review/comparison: `True`
 8+
 9+## Runtime Summary
10+- Status: `PASS`
11+- Scenarios: interface, smoke, dynamics, sedimentation, snapshot
12+- Output modes observed: full, degraded, minimal
13+
14+## Interface Checks
15+- Status: `PASS`
16+- Passed: `2`
17+- Failed: `0`
18+- `PASS` runtime_initializes: A fresh runtime exposes minimal output mode and no active region.
19+- `PASS` locked_interface_presence: Branch A exposes the locked runtime interface required by the spec.
20+
21+## Smoke Checks
22+- Status: `PASS`
23+- Passed: `4`
24+- Failed: `0`
25+- `PASS` ingest_queues_signal: Ingest stores external tokens and anchor hints before the step loop runs.
26+- `PASS` step_materializes_graph_state: Stepping from a queued input creates observable phi/mu/J state.
27+- `PASS` emit_queues_output_feedback: Emit creates a real output-to-input feedback signal rather than a log-only artifact.
28+- `PASS` feedback_changes_later_state: Queued feedback is applied on the next step and changes later runtime state.
29+
30+## Dynamics Checks
31+- Status: `PASS`
32+- Passed: `4`
33+- Failed: `0`
34+- `PASS` phi_mu_J_are_observable: Multi-step dynamics produce visible changes across phi, mu, and J.
35+- `PASS` homing_signals_are_populated: The runtime exposes bound_ability_core, anchor_pull, and drift_score as active homing signals.
36+- `PASS` decay_and_forgetting_are_visible: Decay/forgetting remains real and observable through decay events and stage demotion.
37+- `PASS` degraded_output_modes_exist: The runtime emits full, degraded, and minimal outputs under different runtime conditions.
38+
39+## Sedimentation Checks
40+- Status: `PASS`
41+- Passed: `4`
42+- Failed: `0`
43+- `PASS` sedimentation_trace_exists: Sedimentation history is exported as explicit runtime trace entries.
44+- `PASS` stage_progression_matches_locked_path: Repeated activation follows the locked memory -> experience -> skill_belt -> ability_core path.
45+- `PASS` skill_belt_candidates_have_evidence: Skill-belt candidates are backed by repeated activation, stability, and flow evidence.
46+- `PASS` merge_events_are_recorded: Stable skill-belt structures can produce explicit merge events into ability-core structures.
47+
48+## Snapshot Checks
49+- Status: `PASS`
50+- Passed: `4`
51+- Failed: `0`
52+- `PASS` locked_snapshot_fields_present: snapshot_state returns the locked comparable field set.
53+- `PASS` summary_fields_are_meaningful: phi_summary, mu_summary, and J_summary expose non-empty observable summaries after activity.
54+- `PASS` feedback_and_output_fields_are_populated: snapshot_state exposes output_mode and feedback_effect with applied feedback evidence.
55+- `PASS` locked_homing_and_sedimentation_fields_are_populated: The locked homing and sedimentation-facing snapshot fields are populated under a controlled scenario.
56+
57+## Known Limitations
58+- The validation harness is scenario-based and compact; it is not a benchmark or long-run stability suite.
59+- Checks focus on the locked observable runtime surface rather than richer semantic task performance.
60+- Sedimentation and homing remain explicit but heuristic, which is acceptable for the review/comparison stage.
61+
62+## Readiness
63+- Branch A validation passed and is ready for review/comparison.
M tasks/2026-03-31_task03_branch_a_sedimentation.md
+2, -0
1@@ -120,3 +120,5 @@ Those documents remain locked and define the Branch A requirements for graph-nat
2   - unified validation/reporting against the locked comparison format is still pending
3   - sedimentation merge logic is explicit and inspectable, but still heuristic and node/region centric rather than richer loop-level structure analysis
4   - snapshot observability is ready for Task 04, but the final cross-branch report/export layer is not yet implemented
5+
6+Task 04 for Branch A was then branched independently from commit `164c06af63812c69cab32d8f8a6c770b96f38ef6` on `branch-a/task04-validation-reporting` to add unified validation/reporting and review-ready exports.
A tasks/2026-03-31_task04_branch_a_validation_reporting.md
+130, -0
  1@@ -0,0 +1,130 @@
  2+# Task 04: Branch A Validation and Reporting
  3+
  4+## Title
  5+
  6+Task 04: unified validation + reporting against locked spec
  7+
  8+## Direct Prompt
  9+
 10+Continue Branch A from commit `164c06af63812c69cab32d8f8a6c770b96f38ef6`, keep the implementation independent from other branches, add a small unified validation/reporting entrypoint against the locked docs, generate machine-readable and human-readable reports, and record execution results in-repo.
 11+
 12+## Suggested Branch Name
 13+
 14+`branch-a/task04-validation-reporting`
 15+
 16+## Goal
 17+
 18+Implement the smallest explicit Branch A validation/reporting layer that checks the locked runtime surface, summarizes smoke/dynamics/sedimentation readiness in one schema, and documents Branch A as ready for review/comparison.
 19+
 20+## Background
 21+
 22+This round continues to follow the locked conceptual and engineering constraints in:
 23+
 24+- `/Users/george/code/CIE-Unified/README.md`
 25+- `/Users/george/code/CIE-Unified/LOCKED_IMPLEMENTATION_SPEC.md`
 26+
 27+Those documents remain locked and define the Branch A requirements for graph-native runtime state, the unified interface, output-to-input feedback, homing, decay, degraded output, sedimentation observability, and comparable reporting.
 28+
 29+## Involved Repo
 30+
 31+- `/Users/george/code/CIE-Unified`
 32+
 33+## Scope
 34+
 35+- update the Branch A plan for Task 04 completion
 36+- create this Task 04 prompt document in-repo
 37+- add a stdlib-only `cie.validation` entrypoint for compact controlled scenarios
 38+- validate smoke, dynamics, sedimentation, and locked snapshot requirements in a consistent schema
 39+- generate a machine-readable JSON report and a human-readable Markdown report
 40+- add tests for the validation entrypoint and report schema
 41+- append the Task 04 continuation note to the Task 03 record
 42+- leave Branch A documented as ready for review/comparison rather than adding new runtime subsystems
 43+
 44+## Allowed Modifications
 45+
 46+- `/Users/george/code/CIE-Unified/plans/2026-03-31_branch_a_plan.md`
 47+- `/Users/george/code/CIE-Unified/tasks/2026-03-31_task03_branch_a_sedimentation.md`
 48+- `/Users/george/code/CIE-Unified/tasks/2026-03-31_task04_branch_a_validation_reporting.md`
 49+- `/Users/george/code/CIE-Unified/reports/2026-03-31_task04_branch_a_validation.md`
 50+- `/Users/george/code/CIE-Unified/reports/2026-03-31_task04_branch_a_validation.json`
 51+- `/Users/george/code/CIE-Unified/cie/__init__.py`
 52+- `/Users/george/code/CIE-Unified/cie/runtime.py`
 53+- `/Users/george/code/CIE-Unified/cie/validation.py`
 54+- `/Users/george/code/CIE-Unified/tests/__init__.py`
 55+- `/Users/george/code/CIE-Unified/tests/test_smoke.py`
 56+- `/Users/george/code/CIE-Unified/tests/test_dynamics.py`
 57+- `/Users/george/code/CIE-Unified/tests/test_sedimentation.py`
 58+- `/Users/george/code/CIE-Unified/tests/test_validation.py`
 59+
 60+## Avoid Modifying
 61+
 62+- `/Users/george/code/CIE-Unified/README.md`
 63+- `/Users/george/code/CIE-Unified/LOCKED_IMPLEMENTATION_SPEC.md`
 64+
 65+## Must Complete
 66+
 67+- mark Task 03 complete and Task 04 complete in the Branch A plan
 68+- create this Task 04 prompt document in-repo
 69+- implement a unified validation/reporting entrypoint for Branch A
 70+- validate smoke + dynamics + sedimentation expectations in one consistent report schema
 71+- check locked `snapshot_state()` fields and required runtime behaviors
 72+- generate `/Users/george/code/CIE-Unified/reports/2026-03-31_task04_branch_a_validation.json`
 73+- generate `/Users/george/code/CIE-Unified/reports/2026-03-31_task04_branch_a_validation.md`
 74+- run the recommended validation commands
 75+- record execution details and remaining review-stage limitations here
 76+
 77+## Acceptance Criteria
 78+
 79+1. there is a unified validation/reporting entrypoint for Branch A
 80+2. validation covers smoke + dynamics + sedimentation expectations in a consistent schema
 81+3. validation checks locked `snapshot_state` fields and required runtime behaviors
 82+4. a machine-readable JSON report is generated
 83+5. a human-readable Markdown report is generated
 84+6. tests pass
 85+7. Branch A is documented as ready for review/comparison
 86+
 87+## Evaluation Requirements
 88+
 89+- use only the Python standard library in new runtime/validation code
 90+- keep Branch A graph-native with `(phi, mu, J)` as the canonical state
 91+- keep state minimal, parameters few, and observability explicit
 92+- avoid `exact_text_map`
 93+- avoid MoE-style substitution
 94+- avoid latent-vector ontology as the real runtime state
 95+- prefer an explicit readable harness over a larger framework
 96+
 97+## Recommended Validation Commands
 98+
 99+- `python3 -m unittest discover -s tests -v`
100+- `python3 -m cie.validation`
101+
102+## Delivery Requirements
103+
104+- commit on `branch-a/task04-validation-reporting`
105+- push the branch to `origin`
106+- keep the implementation independent from `branch-b` and other later branches
107+- include execution record details for branch/base/backup/files/validation/results/limitations
108+- leave Branch A ready for review/comparison rather than feature-expanded
109+
110+## Execution Record
111+
112+- actual branch name: `branch-a/task04-validation-reporting`
113+- base commit: `164c06af63812c69cab32d8f8a6c770b96f38ef6`
114+- backup path used for dirty-worktree handling: `none`
115+- files changed:
116+  - `/Users/george/code/CIE-Unified/plans/2026-03-31_branch_a_plan.md`
117+  - `/Users/george/code/CIE-Unified/tasks/2026-03-31_task03_branch_a_sedimentation.md`
118+  - `/Users/george/code/CIE-Unified/tasks/2026-03-31_task04_branch_a_validation_reporting.md`
119+  - `/Users/george/code/CIE-Unified/cie/validation.py`
120+  - `/Users/george/code/CIE-Unified/tests/test_validation.py`
121+  - `/Users/george/code/CIE-Unified/reports/2026-03-31_task04_branch_a_validation.json`
122+  - `/Users/george/code/CIE-Unified/reports/2026-03-31_task04_branch_a_validation.md`
123+- validation commands:
124+  - `python3 -m unittest discover -s tests -v`
125+  - `python3 -m cie.validation`
126+- concise test summary: `Ran 18 tests; all passed.`
127+- concise report summary: `Validation report status PASS; interface, smoke, dynamics, sedimentation, and snapshot sections all passed; Branch A is ready for review/comparison.`
128+- remaining known limitations for review stage:
129+  - the validation harness is compact and scenario-based rather than a benchmark or long-run stability suite
130+  - checks target the locked observable runtime surface, not richer semantic task-performance evaluation
131+  - homing and sedimentation remain explicit but heuristic, which is acceptable for this comparison-ready stage
A tests/test_validation.py
+74, -0
 1@@ -0,0 +1,74 @@
 2+from __future__ import annotations
 3+
 4+import json
 5+import subprocess
 6+import sys
 7+import tempfile
 8+import unittest
 9+from pathlib import Path
10+
11+from cie.validation import (
12+    DEFAULT_JSON_REPORT_PATH,
13+    DEFAULT_MARKDOWN_REPORT_PATH,
14+    REPORT_TOP_LEVEL_KEYS,
15+    generate_validation_report,
16+)
17+
18+
19+REPO_ROOT = Path(__file__).resolve().parent.parent
20+
21+
22+class ValidationReportTests(unittest.TestCase):
23+    def test_validation_entrypoint_runs_with_default_paths(self) -> None:
24+        completed = subprocess.run(
25+            [sys.executable, "-m", "cie.validation"],
26+            cwd=REPO_ROOT,
27+            capture_output=True,
28+            text=True,
29+        )
30+        self.assertEqual(completed.returncode, 0, msg=completed.stderr)
31+        self.assertTrue(DEFAULT_JSON_REPORT_PATH.exists())
32+        self.assertTrue(DEFAULT_MARKDOWN_REPORT_PATH.exists())
33+
34+    def test_json_report_has_stable_top_level_keys(self) -> None:
35+        with tempfile.TemporaryDirectory() as temp_dir:
36+            json_path = Path(temp_dir) / "validation.json"
37+            markdown_path = Path(temp_dir) / "validation.md"
38+            report = generate_validation_report(json_path, markdown_path)
39+            self.assertEqual(tuple(report), REPORT_TOP_LEVEL_KEYS)
40+            payload = json.loads(json_path.read_text(encoding="utf-8"))
41+            self.assertEqual(tuple(payload), REPORT_TOP_LEVEL_KEYS)
42+            self.assertEqual(payload["overall_status"]["status"], "pass")
43+            self.assertTrue(payload["overall_status"]["ready_for_review"])
44+
45+    def test_markdown_report_contains_pass_fail_style_checks(self) -> None:
46+        with tempfile.TemporaryDirectory() as temp_dir:
47+            json_path = Path(temp_dir) / "validation.json"
48+            markdown_path = Path(temp_dir) / "validation.md"
49+            generate_validation_report(json_path, markdown_path)
50+            markdown = markdown_path.read_text(encoding="utf-8")
51+            self.assertIn("# Branch A Validation Report", markdown)
52+            self.assertIn("## Smoke Checks", markdown)
53+            self.assertIn("`PASS`", markdown)
54+            self.assertIn("ready for review/comparison", markdown.lower())
55+
56+    def test_report_sections_expose_expected_schema(self) -> None:
57+        with tempfile.TemporaryDirectory() as temp_dir:
58+            json_path = Path(temp_dir) / "validation.json"
59+            markdown_path = Path(temp_dir) / "validation.md"
60+            report = generate_validation_report(json_path, markdown_path)
61+            for key in (
62+                "interface_checks",
63+                "smoke_checks",
64+                "dynamics_checks",
65+                "sedimentation_checks",
66+                "snapshot_checks",
67+            ):
68+                section = report[key]
69+                self.assertEqual(section["status"], "pass")
70+                self.assertIn("checks", section)
71+                self.assertGreater(len(section["checks"]), 0)
72+
73+
74+if __name__ == "__main__":
75+    unittest.main()