ThomasHeisig commited on
Commit
cd55aa2
·
verified ·
1 Parent(s): 8bd3fb7

Sync GitHub main 6bb97fb9a95926f8118bd61bec366af28afa0e5c (part 7)

Browse files
research/reviews/DETERMINISM_REPRODUCTION_20261024/artifacts/stderr-0.txt ADDED
File without changes
research/reviews/DETERMINISM_REPRODUCTION_20261024/artifacts/stderr-1.txt ADDED
File without changes
research/reviews/DETERMINISM_REPRODUCTION_20261024/artifacts/stderr-42.txt ADDED
File without changes
research/reviews/DETERMINISM_REPRODUCTION_20261024/artifacts/stdout-0.txt ADDED
File without changes
research/reviews/DETERMINISM_REPRODUCTION_20261024/artifacts/stdout-1.txt ADDED
File without changes
research/reviews/DETERMINISM_REPRODUCTION_20261024/artifacts/stdout-42.txt ADDED
File without changes
research/reviews/DETERMINISM_REPRODUCTION_20261024/reproduce.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Source-bound reproduction; invoke with CLEAN_CHECKOUT OUTPUT_DIRECTORY."""
2
+ import dataclasses
3
+ import datetime
4
+ import gzip
5
+ import hashlib
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import platform
10
+ import subprocess
11
+ import sys
12
+
13
+
14
+ def digest(data):
15
+ return hashlib.sha256(data).hexdigest()
16
+
17
+
18
+ def write(path, value):
19
+ path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8", newline="\n")
20
+
21
+
22
+ root = Path(sys.argv[1]).resolve()
23
+ output = Path(sys.argv[2]).resolve()
24
+ if len(sys.argv) > 3 and sys.argv[3] == "--worker":
25
+ sys.path.insert(0, str(root))
26
+ import yaml
27
+ from src.research.experiment_suite import run_ping_v2
28
+ config = yaml.safe_load((root / "configs/learning_experiment.yaml").read_text(encoding="utf-8"))
29
+ write(output, [dataclasses.asdict(run) for run in run_ping_v2(config, seeds=(101, 102, 103), ticks=256)])
30
+ sys.exit(0)
31
+
32
+ output.mkdir(parents=True, exist_ok=False)
33
+
34
+
35
+ def git(*args):
36
+ return subprocess.check_output(["git", "-c", f"safe.directory={root.as_posix()}", "-c", "core.autocrlf=false", *args], cwd=root).decode("utf-8").strip()
37
+
38
+
39
+ assert not git("status", "--porcelain", "--untracked-files=all"), "Checkout must be clean"
40
+ files = {}
41
+ for line in git("ls-files", "-s", "src", "configs").splitlines():
42
+ metadata, name = line.split("\t", 1)
43
+ blob = metadata.split()[1]
44
+ data = (root / name).read_bytes()
45
+ actual_blob = hashlib.sha1(b"blob " + str(len(data)).encode() + b"\0" + data).hexdigest()
46
+ assert actual_blob == blob, f"Non-exact source bytes: {name}"
47
+ files[name] = {"git_blob": blob, "sha256": digest(data)}
48
+ historical = root / "research/experiments/EXP-BATCH-20260914074039-03"
49
+ config_hash = files["configs/learning_experiment.yaml"]["sha256"]
50
+ old_manifest = json.loads((historical / "manifest.json").read_text())
51
+ assert config_hash == old_manifest["config"]["sha256"], "Historical config differs"
52
+ freeze = {
53
+ "created_before_execution_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(),
54
+ "commit": git("rev-parse", "HEAD"), "tree": git("rev-parse", "HEAD^{tree}"),
55
+ "status_before": git("status", "--porcelain", "--untracked-files=all"),
56
+ "source_and_config": files, "config_matches_historical": True,
57
+ "protocol": "deterministic_replica_v1", "runner": "src.research.experiment_suite.run_ping_v2",
58
+ "seeds": [101, 102, 103], "ticks": 256,
59
+ "python": sys.version, "executable": sys.executable, "platform": platform.platform(),
60
+ "machine": platform.machine(), "processor": platform.processor(),
61
+ "harness_sha256": digest(Path(__file__).read_bytes()),
62
+ "command": [sys.executable, *sys.argv],
63
+ "comparison": "All metrics plus before/after state digests in A/B pairs; historical four manuscript totals; full output across fresh processes",
64
+ "scientific_evidence": False,
65
+ }
66
+ write(output / "freeze.json", freeze)
67
+ (output / "pip-freeze.txt").write_bytes(subprocess.check_output([sys.executable, "-m", "pip", "freeze", "--all"]))
68
+ processes = []
69
+ for hashseed in (0, 1, 42):
70
+ target = output / f"runs-hashseed-{hashseed}.json"
71
+ command = [sys.executable, "-B", str(Path(__file__).resolve()), str(root), str(target), "--worker"]
72
+ env = dict(os.environ, PYTHONHASHSEED=str(hashseed), PYTHONDONTWRITEBYTECODE="1")
73
+ result = subprocess.run(command, cwd=root, env=env, capture_output=True)
74
+ (output / f"stdout-{hashseed}.txt").write_bytes(result.stdout)
75
+ (output / f"stderr-{hashseed}.txt").write_bytes(result.stderr)
76
+ processes.append({"command": command, "PYTHONHASHSEED": hashseed, "exit_code": result.returncode})
77
+ write(output / "processes.json", processes)
78
+ if result.returncode:
79
+ raise RuntimeError(result.stderr.decode(errors="replace"))
80
+
81
+ runs = json.loads((output / "runs-hashseed-0.json").read_text())
82
+ checks = []
83
+ keys = ("total_spikes", "delivered_synaptic_events", "recurrent_events", "propagation_depth")
84
+ for seed in (101, 102, 103):
85
+ for arm, expected in (("recurrence_off", [3, 2, 0, 1]), ("recurrence_on", [33, 33, 10, 61])):
86
+ a, b = [next(r for r in runs if r["seed"] == seed and r["condition"] == f"{arm}_replica_{replica}") for replica in ("a", "b")]
87
+ old = json.loads(gzip.decompress(next((historical / "DATA/raw").glob(f"*{arm}_replica_a-seed-{seed}.json.gz")).read_bytes()))
88
+ checks.append({"seed": seed, "arm": arm,
89
+ "all_metrics_equal": a["metrics"] == b["metrics"],
90
+ "initial_state_equal": a["state_digest_before"] == b["state_digest_before"],
91
+ "final_state_equal": a["state_digest_after"] == b["state_digest_after"],
92
+ "ticks_satisfied": a["metrics"]["ticks_executed"] == b["metrics"]["ticks_executed"] == 256,
93
+ "no_runtime_errors": a["runtime_error"] is None and b["runtime_error"] is None,
94
+ "observed": [a["metrics"][key] for key in keys], "expected": expected,
95
+ "historical_totals": [old["metrics"][key] for key in keys]})
96
+ equal_processes = len({digest((output / f"runs-hashseed-{seed}.json").read_bytes()) for seed in (0, 1, 42)}) == 1
97
+ status_after = git("status", "--porcelain", "--untracked-files=all")
98
+ passed = equal_processes and not status_after and all(all(c[k] for k in ("all_metrics_equal", "initial_state_equal", "final_state_equal", "ticks_satisfied", "no_runtime_errors")) and c["observed"] == c["expected"] == c["historical_totals"] for c in checks)
99
+ write(output / "result.json", {"passed": passed, "checks": checks, "fresh_process_outputs_identical": equal_processes, "status_after": status_after, "metric_order": keys, "run_count": 36, "pair_count": 18})
100
+ write(output / "sha256sums.json", {p.name: digest(p.read_bytes()) for p in sorted(output.iterdir()) if p.is_file()})
101
+ print(json.dumps({"passed": passed, "checks": checks, "fresh_process_outputs_identical": equal_processes}, indent=2))
102
+ sys.exit(0 if passed else 1)
research/reviews/MANUSCRIPT_FOLLOWUP_20261024/README.md ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Manuscript-review follow-up — 2026-10-24
2
+
3
+ This work follows the five remaining items of the 2026-10-17 TODO. It is
4
+ recorded on a local branch; publication and independent review are not implied.
5
+
6
+ | Item | Delivered | Remaining boundary |
7
+ | --- | --- | --- |
8
+ | 10% memory compression | [R1 protocol](../../preregistrations/PREREG-MEM-COMPRESSION-R1.md): complete accounting categories, matched-byte controls, 95% retention/non-inferiority rule, stop and architecture decision rules | REVIEW_READY_NOT_FROZEN: canonical RQ/H admission, byte-accounting/restore implementation tests, dataset/source freeze and R1 human approval remain. No experiment executed. |
9
+ | H-EMB-001-B | [V4 report](../../experiments/EXP-EMB001B-UNSEEN-V4-20261024/report.md): 120 runs, disturbance unknown to donor tape, matched causal controls, all integrity checks passed | Exploratory synthetic DATA only; human scientific review and EVID promotion pending. |
10
+ | 5D ablation | [R2 design](../../preregistrations/PREREG-5D-MATCHED-R2.md) and [hash freeze](../../preregistrations/PREREG-5D-MATCHED-R2.freeze.json) | Design frozen before evaluation; dedicated executable R2 runner, preflight and authorization still required. Does not bypass blocked efficiency R1. |
11
+ | Prior art | [Source-based assessment](../PRIOR_ART_REVIEW_20261024.md), explicit antecedents and bounded novelty wording | Independent external reviewer has not reviewed it; the independence TODO remains open. |
12
+ | Maturity percentages | [Method](../../../docs/05-quality/SCIENTIFIC_MATURITY_METHOD.md), arithmetic audit, corrected dashboard and enforcing checker | Status labels remain judgment-based; no evidence promotion and no probability interpretation. Historical editions unchanged. |
13
+
14
+ The memorandum is an addendum to edition 1.8, not a silent rewriting of its
15
+ published historical results. New DATA should enter a future publication only
16
+ with the scoped limitations and review status above. Source-bound experiment
17
+ details live in its own freeze record, not in the later documentation commit.
18
+
19
+ The memory R1 plan is concrete enough for methodological review but cannot be
20
+ called a full freeze merely because its prose is finished. The implementation
21
+ work listed there is required before an execution request. No result-dependent
22
+ choice, favorable outcome or automatic score change substitutes for review.
23
+
24
+ Validation: 43 focused tests passed (embodiment compatibility/controls,
25
+ publication maturity contracts and deliberate score-corruption cases).
26
+ Ruff, JavaScript syntax, documentation consistency and scientific-maturity
27
+ integrity checks passed. `verify_emb001b_unseen_v4.py` independently recomputes
28
+ the primary outcome from all 120 stored traces and checks archived hashes,
29
+ causal sentinels and source hashes against the pre-execution Git commit.
30
+ This second computation is an internal verifier, not independent peer review.
research/reviews/MANUSCRIPT_FOLLOWUP_20261024/maturity-score-audit.json ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source_commit": "fdfd0568",
3
+ "criteria_changed": false,
4
+ "evidence_promoted": false,
5
+ "stages": [
6
+ {
7
+ "stage": 0,
8
+ "previous_score": 0.925,
9
+ "weighted_status_score": 0.925,
10
+ "difference": 0.0
11
+ },
12
+ {
13
+ "stage": 1,
14
+ "previous_score": 0.85,
15
+ "weighted_status_score": 0.85,
16
+ "difference": 0.0
17
+ },
18
+ {
19
+ "stage": 2,
20
+ "previous_score": 0.65,
21
+ "weighted_status_score": 0.75,
22
+ "difference": 0.1
23
+ },
24
+ {
25
+ "stage": 3,
26
+ "previous_score": 0.55,
27
+ "weighted_status_score": 0.65,
28
+ "difference": 0.1
29
+ },
30
+ {
31
+ "stage": 4,
32
+ "previous_score": 0.55,
33
+ "weighted_status_score": 0.65,
34
+ "difference": 0.1
35
+ },
36
+ {
37
+ "stage": 5,
38
+ "previous_score": 0.55,
39
+ "weighted_status_score": 0.65,
40
+ "difference": 0.1
41
+ },
42
+ {
43
+ "stage": 6,
44
+ "previous_score": 0.4,
45
+ "weighted_status_score": 0.45,
46
+ "difference": 0.05
47
+ },
48
+ {
49
+ "stage": 7,
50
+ "previous_score": 0.28,
51
+ "weighted_status_score": 0.325,
52
+ "difference": 0.045
53
+ },
54
+ {
55
+ "stage": 8,
56
+ "previous_score": 0.23,
57
+ "weighted_status_score": 0.4,
58
+ "difference": 0.17
59
+ },
60
+ {
61
+ "stage": 9,
62
+ "previous_score": 0.15,
63
+ "weighted_status_score": 0.3,
64
+ "difference": 0.15
65
+ },
66
+ {
67
+ "stage": 10,
68
+ "previous_score": 0.13,
69
+ "weighted_status_score": 0.3,
70
+ "difference": 0.17
71
+ }
72
+ ]
73
+ }
research/reviews/PRIOR_ART_REVIEW_20261024.md ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Prior-art assessment of the methodology claims
2
+
3
+ 2026-10-24. **AI-assisted source review; independent external review PENDING.**
4
+ The assistant preparing this review also changed repository methods and
5
+ documentation. It is not an independent reviewer. No external person has
6
+ been contacted or represented as having approved this assessment.
7
+
8
+ ## Search and inclusion record
9
+
10
+ Web search on 2026-10-24, queries: “research provenance W3C PROV
11
+ recommendation 2013 executable paper computational reproducibility Knuth
12
+ 1984 literate programming”; “Nosek 2018 preregistration revolution reflexivity
13
+ scientific research positionality Finlay 2002”; “eLife executable research
14
+ 2020”; “Jupyter Notebooks 2016 Kluyver”; “site.cos.io registered reports peer
15
+ review before results”. Included original standards, author publications,
16
+ institutional author deposits and publisher descriptions directly addressing
17
+ one of the four claims. General blog summaries and search aggregators were
18
+ excluded. This is a targeted scoping review, not an exhaustive systematic
19
+ search or a patent novelty opinion. Ranking, language and database coverage
20
+ can omit relevant work; absence from this table is not absence of prior art.
21
+
22
+ ## Claim-to-source comparison
23
+
24
+ | MHRN claim family | Prior art and verified scope | Consequence for novelty |
25
+ | --- | --- | --- |
26
+ | Research provenance | [W3C PROV-DM (2013)](https://www.w3.org/TR/prov-dm/) models entities, activities, agents and derivation. | Recording origins and transformations is established. MHRN's hashes, DATA/EVID distinctions and domain-specific gates are implementation choices; they do not invent provenance. No formal PROV interoperability has been demonstrated here. |
27
+ | Preregistration | [Nosek et al. (2018)](https://pmc.ncbi.nlm.nih.gov/articles/5856500/) distinguish prospective predictions/analysis plans from postdiction. [COS Registered Reports](https://www.cos.io/initiatives/registered-reports) describes review before data collection. | Frozen plans and separating exploratory from confirmatory work are established. A local Git freeze is not independent registry deposit or a journal's in-principle acceptance. |
28
+ | Executable publication | [Knuth's author account of literate programming](https://cs.stanford.edu/~knuth/lp.html) combines program and documentation; [Kluyver et al. (2016)](https://escholarship.org/uc/item/08b3d4s2) presents notebooks as reproducible-workflow publication; [eLife's ERA launch (2020)](https://elifesciences.org/for-the-press/eb096af1/elife-launches-executable-research-articles-for-publishing-computationally-reproducible-results) embeds executable code and outputs. | Linking narrative, code and results is not new. MHRN's source-bound publication pipeline is a project-specific realization; an exportable manuscript or artifact link alone is not an in-browser executable article. |
29
+ | Reflexive methodology | [Finlay (2002)](https://doi.org/10.1177/146879410200200205) discusses several forms of researcher reflexivity and their difficulties. Only the publisher's abstract and accessible page were inspected. | Documenting researcher influence, choices and limitations has substantial antecedents. AI participation adds a context needing evaluation, not an automatic claim of a new epistemology. |
30
+ | Independent verification | [COS TOP Guidelines](https://www.cos.io/initiatives/top-guidelines) distinguishes verification by an independent party, reproducibility and replication. | Passing local checks or reusing a different software implementation does not by itself establish independent replication. |
31
+
32
+ All descriptions above are paraphrases. The Knuth original journal DOI
33
+ `10.1093/comjnl/27.2.97` was identified, but its full publisher page could not
34
+ be retrieved; the author's accessible overview is used for the limited claim.
35
+ No inaccessible full text is presented as read.
36
+
37
+ ## Assessment of the actual manuscript claim
38
+
39
+ Edition 1.8, `parts/10_synthesis.md`, locates the project's contribution in
40
+ integration of research software engineering and governance: RQ/H registry,
41
+ preregistration, source-bound DATA, human review, EVID status, provenance,
42
+ test adequacy and viewer. That modest integration claim is more defensible
43
+ than claiming invention of any component. It remains a description of work
44
+ performed, not proof that its combination is unprecedented or improves
45
+ scientific quality compared with existing workflows.
46
+
47
+ Recommended wording for subsequent editions:
48
+
49
+ > MHRN implements a domain-specific integration of established provenance,
50
+ > preregistration, reproducible-computation and reflexive-documentation
51
+ > practices. Its contribution is the explicit implementation and audit of
52
+ > these practices in this evolving SNN programme. Priority of the combination
53
+ > and comparative gains in research quality have not been established.
54
+
55
+ This recommendation does not silently rewrite an already published edition.
56
+ To establish a stronger claim, compare MHRN against established workflow and
57
+ publication systems on predefined capabilities and failure cases. Include
58
+ maintenance cost, usability, audit error rates and negative outcomes; a
59
+ feature inventory alone is not a causal evaluation of governance quality.
60
+
61
+ ## External review handoff
62
+
63
+ An independent reviewer should disclose involvement/conflicts, expand the
64
+ search beyond these starting sources, inspect actual implementation and
65
+ manuscript passages, distinguish conceptual antecedents from engineering
66
+ differences, and return a dated signed assessment with cited counterexamples.
67
+ Acceptance requires a review artifact, reviewer identity/role, search scope,
68
+ claim-by-claim decisions and explicit unresolved limitations. Until then the
69
+ TODO's independence requirement remains open; this preparation is complete.
review_portal/build_static.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Package only public questionnaire assets, with online collection disabled."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import hashlib
7
+ import json
8
+ from pathlib import Path
9
+
10
+ ROOT = Path(__file__).resolve().parents[1]
11
+ SOURCE = ROOT / "src/dashboard/static/review"
12
+ FILES = ("index.html", "app.js", "model.js", "instrument.js", "review.css")
13
+
14
+
15
+ def build(output: Path) -> dict[str, object]:
16
+ """Create a new directory; never publish admin pages, secrets or response data."""
17
+ contents = {name: (SOURCE / name).read_bytes() for name in FILES}
18
+ html = contents["index.html"].decode("utf-8")
19
+ html = html.replace('<html lang="de">', '<html lang="de" data-review-mode="export-only">')
20
+ html = html.replace('="/review/', '="./')
21
+ html = html.replace("connect-src 'self'", "connect-src 'none'")
22
+ html = html.replace(
23
+ '<a href="./admin.html">Erhebungsverwaltung</a>',
24
+ '<a href="./privacy.html">Datenschutz und Exportmodus</a>',
25
+ )
26
+ if 'data-review-mode="export-only"' not in html or "admin.html" in html:
27
+ raise ValueError("Static export contract changed; inspect the source HTML")
28
+ contents["index.html"] = html.encode("utf-8")
29
+ contents["review.css"] += (
30
+ b'\nhtml[data-review-mode="export-only"] .withdrawal { display: none; }\n'
31
+ )
32
+ contents["privacy.html"] = (ROOT / "review_portal/privacy-export.html").read_bytes()
33
+ contents[".nojekyll"] = b""
34
+ manifest = {
35
+ "mode": "export-only",
36
+ "collection_enabled": False,
37
+ "files_sha256": {
38
+ name: hashlib.sha256(data).hexdigest() for name, data in contents.items()
39
+ },
40
+ "independent_review": "not_asserted",
41
+ }
42
+ output.mkdir(parents=True, exist_ok=False)
43
+ for name, data in contents.items():
44
+ (output / name).write_bytes(data)
45
+ (output / "deployment-manifest.json").write_text(
46
+ json.dumps(manifest, indent=2) + "\n", encoding="utf-8", newline="\n"
47
+ )
48
+ return manifest
49
+
50
+
51
+ def main() -> None:
52
+ parser = argparse.ArgumentParser(description=__doc__)
53
+ parser.add_argument("--output", type=Path, required=True)
54
+ args = parser.parse_args()
55
+ print(json.dumps(build(args.output), indent=2))
56
+
57
+
58
+ if __name__ == "__main__":
59
+ main()
review_portal/privacy-export.html ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
3
+ <meta name="referrer" content="no-referrer">
4
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'self'; base-uri 'none'; form-action 'none'">
5
+ <title>MHRN — Datenschutz und Exportmodus</title><link rel="stylesheet" href="./review.css"></head>
6
+ <body><header class="masthead"><a class="brand" href="./index.html">MHRN — zum Fragebogen</a></header>
7
+ <main><section class="card"><h1>Datenschutz und lokaler Export</h1>
8
+ <p>Stand: 24. Oktober 2026. Dieses Paket ist ausschließlich für den Exportmodus auf GitHub Pages vorbereitet.</p>
9
+ <h2>Verantwortlich für das Portal</h2>
10
+ <p>Thomas Heisig, Wolffsheide 10, 27777 Ganderkesee, Deutschland. Kontakt: <a href="mailto:t_heisig@gmx.de">t_heisig@gmx.de</a>.</p>
11
+ <h2>Ihre Antworten</h2>
12
+ <p>Es gibt hier keine zentrale Online-Abgabe. Antworten werden im Browser verarbeitet und nicht automatisch an die Projektleitung, GitHub oder einen KI-Dienst versendet. Das Ausfüllen ist freiwillig; persönliche und sensible Angaben sollten vermieden werden. Eine Bewertung ist keine unabhängige Ethikfreigabe oder wissenschaftliche Evidenz.</p>
13
+ <p>Ohne aktivierte Gerätespeicherung bleibt der Entwurf nur im Arbeitsspeicher der Seite. Auf Wunsch können Sie einen unverschlüsselten Entwurf im lokalen Browserspeicher ablegen und mit „Lokalen Entwurf löschen“ entfernen. Von Ihnen heruntergeladene JSON-/CSV-Dateien verwalten und löschen Sie selbst. Browserdaten können verloren gehen; das Portal erstellt keine Antwort-Backups.</p>
14
+ <p>Ein Export ist keine Serverabgabe. Für eine spätere Übermittlung an eine prüfende Person müssen Empfänger, Zweck, sicherer Übermittlungsweg und Aufbewahrung gesondert vereinbart werden. Dieses Portal verlangt keine Zusendung per E-Mail.</p>
15
+ <h2>Hosting und technische Verbindungsdaten</h2>
16
+ <p>Beim Abruf über GitHub Pages erhält der Hostingdienst technische Verbindungsdaten. GitHub erklärt, IP-Adressen von Seitenbesuchern zu Sicherheitszwecken zu protokollieren. Die Aussage „kein Antwortversand“ bedeutet deshalb nicht „keine Verbindungsdaten“.</p>
17
+ <p>Weitere Angaben zu GitHubs Verarbeitung, Speicherdauern, Übermittlungen und Datenschutzrechten stehen in der <a href="https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement" rel="noreferrer">GitHub-Datenschutzerklärung</a> und der <a href="https://docs.github.com/en/pages/getting-started-with-github-pages/what-is-github-pages#data-collection" rel="noreferrer">Pages-Dokumentation</a>. Das Portal verwendet keine eigenen Analyse-Tracker.</p>
18
+ <h2>Fragen und Rechte</h2>
19
+ <p>Für Fragen zur Verarbeitung durch die Projektleitung nutzen Sie den obigen Kontakt. Je nach Verarbeitung bestehen Rechte auf Auskunft, Berichtigung, Löschung, Einschränkung, Datenübertragbarkeit und Widerspruch sowie ein Beschwerderecht bei einer Datenschutzaufsichtsbehörde. Eine etwaige Einwilligung kann für die Zukunft widerrufen werden. Lokale Antworten können wir nicht abrufen oder für Sie löschen.</p>
20
+ <p>Der Exportmodus aktiviert keine zentral gespeicherte Studie. Vor einer Online-Erhebung sind deren konkrete Rechtsgrundlage, Hosting, Zugriff, Fristen und unabhängige Prüfung gesondert festzulegen.</p>
21
+ </section></main></body></html>
review_portal/tests/browser_static.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Exercise the export package under a project subpath with no collector."""
2
+
3
+ from functools import partial
4
+ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
5
+ import os
6
+ from pathlib import Path
7
+ from tempfile import TemporaryDirectory
8
+ from threading import Thread
9
+
10
+ from playwright.sync_api import expect, sync_playwright
11
+
12
+ from review_portal.build_static import build
13
+
14
+
15
+ class QuietHandler(SimpleHTTPRequestHandler):
16
+ def log_message(self, *args):
17
+ pass
18
+
19
+
20
+ def main():
21
+ with TemporaryDirectory(prefix="review-static-") as directory:
22
+ root = Path(directory)
23
+ build(root / "MHRN")
24
+ server = ThreadingHTTPServer(
25
+ ("127.0.0.1", 0), partial(QuietHandler, directory=directory)
26
+ )
27
+ thread = Thread(target=server.serve_forever, daemon=True)
28
+ thread.start()
29
+ try:
30
+ with sync_playwright() as playwright:
31
+ browser = playwright.chromium.launch(
32
+ channel=os.environ.get("MHRN_TEST_BROWSER_CHANNEL")
33
+ )
34
+ page = browser.new_page(accept_downloads=True)
35
+ errors, requests = [], []
36
+ page.on("pageerror", lambda error: errors.append(str(error)))
37
+ page.on("request", lambda request: requests.append(request))
38
+ url = f"http://127.0.0.1:{server.server_port}/MHRN/index.html"
39
+ page.goto(url, wait_until="networkidle")
40
+ expect(page.locator("#mode")).to_contain_text("kein Versand")
41
+ expect(page.locator("a[href='./privacy.html']")).to_be_visible()
42
+ expect(page.locator(".withdrawal")).not_to_be_visible()
43
+ page.locator("#adult").check()
44
+ page.locator("#consent").check()
45
+ page.locator("#start").click()
46
+ page.locator('.question:has(.qid:text-is("A4")) textarea').first.fill(
47
+ "SYNTHETIC EXPORT TEST"
48
+ )
49
+ assert page.evaluate("localStorage.length") == 0
50
+ with page.expect_download() as download:
51
+ page.locator("#save").click()
52
+ assert "SYNTHETIC EXPORT TEST" in Path(download.value.path()).read_text(
53
+ encoding="utf-8"
54
+ )
55
+ page.set_viewport_size({"width": 390, "height": 844})
56
+ assert page.evaluate("document.documentElement.scrollWidth <= innerWidth")
57
+ expect(page.locator("#online")).not_to_be_visible()
58
+ assert not errors, errors
59
+ assert all("/api/" not in r.url and r.method == "GET" for r in requests)
60
+ page.goto(url.replace("index.html", "privacy.html"))
61
+ expect(page.locator("h1")).to_have_text("Datenschutz und lokaler Export")
62
+ browser.close()
63
+ finally:
64
+ server.shutdown()
65
+ server.server_close()
66
+ thread.join(timeout=5)
67
+ print("PASS: project subpath, offline export, privacy page, mobile; zero API requests")
68
+
69
+
70
+ if __name__ == "__main__":
71
+ main()
review_portal/tests/test_static_export.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Static deployment must remain portable and unable to collect responses."""
2
+
3
+ import hashlib
4
+ import json
5
+
6
+ import pytest
7
+
8
+ from review_portal.build_static import FILES, build
9
+
10
+
11
+ def test_only_export_assets_are_packaged(tmp_path):
12
+ output = tmp_path / "MHRN"
13
+ result = build(output)
14
+ assert set(p.name for p in output.iterdir()) == set(FILES) | {
15
+ "privacy.html", ".nojekyll", "deployment-manifest.json"
16
+ }
17
+ html = (output / "index.html").read_text(encoding="utf-8")
18
+ assert 'data-review-mode="export-only"' in html
19
+ assert "connect-src 'none'" in html
20
+ assert '="/review/' not in html
21
+ assert "admin.html" not in html
22
+ assert result["collection_enabled"] is False
23
+ stored = json.loads((output / "deployment-manifest.json").read_text())
24
+ for name, digest in stored["files_sha256"].items():
25
+ assert hashlib.sha256((output / name).read_bytes()).hexdigest() == digest
26
+
27
+
28
+ def test_existing_directory_is_never_published_or_overwritten(tmp_path):
29
+ (tmp_path / "private-response.json").write_text("private")
30
+ with pytest.raises(FileExistsError):
31
+ build(tmp_path)
32
+ assert list(tmp_path.iterdir()) == [tmp_path / "private-response.json"]
scripts/check_scientific_maturity_integrity.py CHANGED
@@ -90,6 +90,25 @@ def main() -> int:
90
  isinstance(criteria, list) and bool(criteria),
91
  f"stage {stage_no} missing scientific criteria",
92
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
  stage6 = next(
95
  (
 
90
  isinstance(criteria, list) and bool(criteria),
91
  f"stage {stage_no} missing scientific criteria",
92
  )
93
+ if isinstance(criteria, list) and isinstance(weights, dict):
94
+ ids = [c.get("id") for c in criteria if isinstance(c, dict)]
95
+ require(
96
+ len(ids) == len(set(ids)) and set(ids) == set(weights),
97
+ f"stage {stage_no} must contain each weighted criterion once",
98
+ )
99
+ scale = {"met": 1.0, "partial": 0.5, "open": 0.0}
100
+ valid = all(
101
+ isinstance(c, dict) and c.get("status") in scale for c in criteria
102
+ )
103
+ require(valid, f"stage {stage_no} has an unsupported criterion status")
104
+ if valid and set(ids) == set(weights):
105
+ expected = sum(
106
+ float(weights[c["id"]]) * scale[c["status"]] for c in criteria
107
+ )
108
+ require(
109
+ abs(score - expected) < 1e-9,
110
+ f"stage {stage_no} score does not match weighted criteria",
111
+ )
112
 
113
  stage6 = next(
114
  (
scripts/check_stage1_frozen_prefreeze.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Revalidate frozen R2 methods without replacing hash-bound audit artifacts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import importlib
7
+ import json
8
+ import sys
9
+ import tempfile
10
+ from pathlib import Path
11
+
12
+ ROOT = Path(__file__).resolve().parents[1]
13
+ sys.path.insert(0, str(ROOT))
14
+
15
+
16
+ def main() -> None:
17
+ freeze = json.loads(
18
+ (
19
+ ROOT / "research/preregistrations/PREREG-S1-TOPO-REFERENCE-R2.freeze.json"
20
+ ).read_text(encoding="utf-8")
21
+ )
22
+ for key in (
23
+ "mechanism_audit",
24
+ "dynamics_reuse_validation",
25
+ "topology_mapping_parity",
26
+ "integrator_parity",
27
+ "reset_parity",
28
+ "synapse_delay_parity",
29
+ ):
30
+ entry = freeze[key]
31
+ path = ROOT / entry["path"].replace("\\", "/")
32
+ if hashlib.sha256(path.read_bytes()).hexdigest() != entry["sha256"]:
33
+ raise ValueError(f"Frozen artifact changed: {key}")
34
+ with tempfile.TemporaryDirectory(prefix="mhrn-r2-revalidation-") as directory:
35
+ for name in (
36
+ "audit_stage1_reference_mechanisms",
37
+ "verify_stage1_reference_dynamics_reuse_r2",
38
+ "run_stage1_reference_topology_mapping_r2",
39
+ ):
40
+ module = importlib.import_module(f"scripts.{name}")
41
+ module.OUT = Path(directory) / f"{name}.json"
42
+ if module.main() != 0:
43
+ raise ValueError(f"Current method revalidation failed: {name}")
44
+ print("PASS: frozen artifacts intact and current R2 method gates passed")
45
+
46
+
47
+ if __name__ == "__main__":
48
+ main()
scripts/freeze_stage1_topology_reference_r2.py CHANGED
@@ -3,9 +3,12 @@
3
 
4
  from __future__ import annotations
5
 
 
6
  import hashlib
7
  import json
8
  import subprocess
 
 
9
  from pathlib import Path
10
 
11
  ROOT = Path(__file__).resolve().parents[1]
@@ -75,6 +78,14 @@ def git(*args):
75
 
76
 
77
  def main() -> int:
 
 
 
 
 
 
 
 
78
  if F.exists():
79
  raise FileExistsError(F)
80
  if git("status", "--porcelain"):
@@ -106,11 +117,11 @@ def main() -> int:
106
  or proto.get("framework_version") != "2.10.1"
107
  ):
108
  raise RuntimeError("R2 protocol mismatch")
109
- subprocess.run(["python", str(IND.relative_to(ROOT))], cwd=ROOT, check=True)
110
- subprocess.run(["python", str(SEED.relative_to(ROOT))], cwd=ROOT, check=True)
111
  subprocess.run(
112
  [
113
- "python",
114
  "-m",
115
  "pytest",
116
  str(CT.relative_to(ROOT)),
@@ -130,7 +141,7 @@ def main() -> int:
130
  "schema_version": 1,
131
  "preregistration_id": p["preregistration_id"],
132
  "supersedes": "PREREG-S1-TOPO-REFERENCE-R1",
133
- "freeze_date": "2026-09-27",
134
  "freeze_commit": git("rev-parse", "HEAD"),
135
  "preregistration_sha256_before_status_update": sha(P),
136
  "reference_data_created": False,
@@ -203,7 +214,11 @@ def main() -> int:
203
  "authorization_rule": p["execution_authorization_requirements"],
204
  "note": "R2 corrects the pre-DATA R1 mapping ambiguity. Freeze fixes the scientific contract only; separate explicit human execution authorization remains mandatory.",
205
  }
206
- F.write_text(json.dumps(freeze, indent=2, sort_keys=True) + "\n", encoding="utf-8")
 
 
 
 
207
  p["status"] = "FROZEN_BEFORE_REFERENCE_EXECUTION_AUTHORIZATION"
208
  p["execution_authorized"] = False
209
  p["freeze_authorization"] = {
@@ -216,7 +231,11 @@ def main() -> int:
216
  p["freeze_semantics"] = (
217
  "R2 scientific contract is frozen. Reference DATA remain forbidden until a separate explicit human execution-authorization record binds this freeze commit and runner hash."
218
  )
219
- P.write_text(json.dumps(p, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
 
 
 
 
220
  print(json.dumps({"status": p["status"], "execution_authorized": False}))
221
  return 0
222
 
 
3
 
4
  from __future__ import annotations
5
 
6
+ import argparse
7
  import hashlib
8
  import json
9
  import subprocess
10
+ import sys
11
+ from datetime import date
12
  from pathlib import Path
13
 
14
  ROOT = Path(__file__).resolve().parents[1]
 
78
 
79
 
80
  def main() -> int:
81
+ parser = argparse.ArgumentParser(description=__doc__)
82
+ parser.add_argument(
83
+ "--freeze-date",
84
+ type=date.fromisoformat,
85
+ required=True,
86
+ help="Actual freeze date (YYYY-MM-DD); never inherit the draft's date",
87
+ )
88
+ args = parser.parse_args()
89
  if F.exists():
90
  raise FileExistsError(F)
91
  if git("status", "--porcelain"):
 
117
  or proto.get("framework_version") != "2.10.1"
118
  ):
119
  raise RuntimeError("R2 protocol mismatch")
120
+ subprocess.run([sys.executable, str(IND.relative_to(ROOT))], cwd=ROOT, check=True)
121
+ subprocess.run([sys.executable, str(SEED.relative_to(ROOT))], cwd=ROOT, check=True)
122
  subprocess.run(
123
  [
124
+ sys.executable,
125
  "-m",
126
  "pytest",
127
  str(CT.relative_to(ROOT)),
 
141
  "schema_version": 1,
142
  "preregistration_id": p["preregistration_id"],
143
  "supersedes": "PREREG-S1-TOPO-REFERENCE-R1",
144
+ "freeze_date": args.freeze_date.isoformat(),
145
  "freeze_commit": git("rev-parse", "HEAD"),
146
  "preregistration_sha256_before_status_update": sha(P),
147
  "reference_data_created": False,
 
214
  "authorization_rule": p["execution_authorization_requirements"],
215
  "note": "R2 corrects the pre-DATA R1 mapping ambiguity. Freeze fixes the scientific contract only; separate explicit human execution authorization remains mandatory.",
216
  }
217
+ F.write_text(
218
+ json.dumps(freeze, indent=2, sort_keys=True) + "\n",
219
+ encoding="utf-8",
220
+ newline="\n",
221
+ )
222
  p["status"] = "FROZEN_BEFORE_REFERENCE_EXECUTION_AUTHORIZATION"
223
  p["execution_authorized"] = False
224
  p["freeze_authorization"] = {
 
231
  p["freeze_semantics"] = (
232
  "R2 scientific contract is frozen. Reference DATA remain forbidden until a separate explicit human execution-authorization record binds this freeze commit and runner hash."
233
  )
234
+ P.write_text(
235
+ json.dumps(p, indent=2, ensure_ascii=False) + "\n",
236
+ encoding="utf-8",
237
+ newline="\n",
238
+ )
239
  print(json.dumps({"status": p["status"], "execution_authorized": False}))
240
  return 0
241
 
scripts/publication_empirical.py CHANGED
@@ -34,7 +34,7 @@ def save(path: Path, value: object) -> None:
34
 
35
  def inventory(path: Path) -> dict[str, str]:
36
  return {
37
- str(p.relative_to(path)): sha(p)
38
  for p in sorted(path.rglob("*"))
39
  if p.is_file() and p.name != "ARTIFACTS.json"
40
  }
 
34
 
35
  def inventory(path: Path) -> dict[str, str]:
36
  return {
37
+ p.relative_to(path).as_posix(): sha(p)
38
  for p in sorted(path.rglob("*"))
39
  if p.is_file() and p.name != "ARTIFACTS.json"
40
  }
scripts/run_emb001b_unseen_v4.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run frozen exploratory V4 to an external, new output directory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import datetime
7
+ import hashlib
8
+ import json
9
+ import math
10
+ import os
11
+ import platform
12
+ import statistics
13
+ import subprocess
14
+ import sys
15
+ import time
16
+ from dataclasses import asdict
17
+ from pathlib import Path
18
+
19
+ ROOT = Path(__file__).resolve().parents[1]
20
+ sys.path.insert(0, str(ROOT))
21
+
22
+ import yaml # noqa: E402
23
+
24
+ from src.research.connectome_embodiment import _simulate # noqa: E402
25
+
26
+
27
+ def save(path, value):
28
+ path.write_text(
29
+ json.dumps(value, indent=2, sort_keys=True, allow_nan=False) + "\n",
30
+ encoding="utf-8",
31
+ newline="\n",
32
+ )
33
+
34
+
35
+ def git(*args):
36
+ return (
37
+ subprocess.check_output(
38
+ ["git", "-c", f"safe.directory={ROOT.as_posix()}", *args], cwd=ROOT
39
+ )
40
+ .decode()
41
+ .strip()
42
+ )
43
+
44
+
45
+ def sha(path):
46
+ return hashlib.sha256(path.read_bytes()).hexdigest()
47
+
48
+
49
+ def main():
50
+ parser = argparse.ArgumentParser()
51
+ parser.add_argument("--output", type=Path, required=True)
52
+ out = parser.parse_args().output.resolve()
53
+ if out.is_relative_to(ROOT):
54
+ raise ValueError("Output must be outside the tested checkout")
55
+ if git("status", "--porcelain"):
56
+ raise ValueError("Clean committed source required")
57
+ p = ROOT / "research/preregistrations/PREREG-EMB001B-UNSEEN-V4.json"
58
+ protocol = json.loads(p.read_text())
59
+ config_path = ROOT / "configs/learning_experiment.yaml"
60
+ config = yaml.safe_load(config_path.read_text())
61
+ out.mkdir(parents=True, exist_ok=False)
62
+ save(
63
+ out / "freeze.json",
64
+ {
65
+ "commit": git("rev-parse", "HEAD"),
66
+ "tree": git("rev-parse", "HEAD^{tree}"),
67
+ "utc_before_execution": datetime.datetime.now(
68
+ datetime.timezone.utc
69
+ ).isoformat(),
70
+ "python": sys.version,
71
+ "platform": platform.platform(),
72
+ "command": [sys.executable, *sys.argv],
73
+ "PYTHONHASHSEED": os.environ.get("PYTHONHASHSEED"),
74
+ "protocol": protocol,
75
+ "status_before": "",
76
+ "hashes": {
77
+ str(f.relative_to(ROOT)): sha(f)
78
+ for f in [
79
+ p,
80
+ config_path,
81
+ Path(__file__),
82
+ *sorted((ROOT / "src").rglob("*.py")),
83
+ ]
84
+ },
85
+ },
86
+ )
87
+ (out / "pip-freeze.txt").write_text(
88
+ subprocess.check_output(
89
+ [sys.executable, "-m", "pip", "freeze", "--all"]
90
+ ).decode(),
91
+ encoding="utf-8",
92
+ newline="\n",
93
+ )
94
+ results, integrity = [], []
95
+ started = time.monotonic()
96
+ for seed in protocol["seeds"]:
97
+ ticks = protocol["ticks"]
98
+ schedule = tuple(
99
+ (0.003 if seed % 2 == 0 else -0.003) if 250 <= t < 500 else 0.0
100
+ for t in range(ticks)
101
+ )
102
+ donor = _simulate(config, seed, ticks, "closed_loop", perturb=False)
103
+ save(out / f"donor-{seed}.json", asdict(donor))
104
+ runs = {
105
+ "donor_unperturbed": donor,
106
+ "yoked_unperturbed": _simulate(
107
+ config, seed, ticks, "yoked_replay", donor=donor, perturb=False
108
+ ),
109
+ "closed_challenge": _simulate(
110
+ config, seed, ticks, "closed_loop", external_torque_schedule=schedule
111
+ ),
112
+ "yoked_challenge": _simulate(
113
+ config,
114
+ seed,
115
+ ticks,
116
+ "yoked_replay",
117
+ donor=donor,
118
+ external_torque_schedule=schedule,
119
+ ),
120
+ "feedback_absent_challenge": _simulate(
121
+ config,
122
+ seed,
123
+ ticks,
124
+ "feedback_absent",
125
+ external_torque_schedule=schedule,
126
+ ),
127
+ "pd_challenge": _simulate(
128
+ config,
129
+ seed,
130
+ ticks,
131
+ "controller_only",
132
+ external_torque_schedule=schedule,
133
+ ),
134
+ }
135
+ y = runs["yoked_challenge"]
136
+ checks = {
137
+ "complete": all(
138
+ r.error is None and r.metrics["ticks_executed"] == ticks
139
+ for r in runs.values()
140
+ ),
141
+ "initial_neural_equal": len({r.before for r in runs.values()}) == 1,
142
+ "initial_body_equal": all(
143
+ r.metrics["gateway_state_before"]
144
+ == donor.metrics["gateway_state_before"]
145
+ for r in runs.values()
146
+ ),
147
+ "graph_equal": all(
148
+ r.metrics["graph"] == donor.metrics["graph"] for r in runs.values()
149
+ ),
150
+ "sentinel_exact": runs["yoked_unperturbed"].metrics["trace"]
151
+ == donor.metrics["trace"],
152
+ "donor_observations_exact": [
153
+ (r["observed_q_rad"], r["observed_omega_rad_s"])
154
+ for r in y.metrics["trace"]
155
+ ]
156
+ == donor.sensor_tape,
157
+ "yoked_commands_unchanged": y.motor_tape == donor.motor_tape,
158
+ "body_responds_to_disturbance": y.sensor_tape != donor.sensor_tape,
159
+ "absent_zero": all(
160
+ r["observed_q_rad"] == r["observed_omega_rad_s"] == 0
161
+ for r in runs["feedback_absent_challenge"].metrics["trace"]
162
+ ),
163
+ }
164
+ integrity.append({"seed": seed, "checks": checks})
165
+ for condition, run in runs.items():
166
+ trace = run.metrics["trace"][250:]
167
+ rmse = math.sqrt(
168
+ statistics.mean((r["target_rad"] - r["q_rad"]) ** 2 for r in trace)
169
+ )
170
+ results.append(
171
+ {
172
+ "seed": seed,
173
+ "condition": condition,
174
+ "post_disturbance_rmse": rmse,
175
+ "full_rmse": run.metrics["tracking_rmse_rad"],
176
+ "error": run.error,
177
+ }
178
+ )
179
+ save(
180
+ out / f"runs-{seed}.json",
181
+ {"schedule": schedule, "runs": {k: asdict(v) for k, v in runs.items()}},
182
+ )
183
+ save(out / "integrity.json", integrity)
184
+ save(out / "outcomes.json", results)
185
+ if (
186
+ time.monotonic() - started > 600
187
+ or sum(f.stat().st_size for f in out.iterdir()) > 100000000
188
+ ):
189
+ raise RuntimeError(
190
+ "Frozen resource ceiling exceeded; partial outputs retained"
191
+ )
192
+ differences = []
193
+ for seed in protocol["seeds"]:
194
+ rows = {r["condition"]: r for r in results if r["seed"] == seed}
195
+ differences.append(
196
+ {
197
+ "seed": seed,
198
+ "difference": rows["yoked_challenge"]["post_disturbance_rmse"]
199
+ - rows["closed_challenge"]["post_disturbance_rmse"],
200
+ }
201
+ )
202
+ summary = {}
203
+ for name, selected in (
204
+ ("pooled", differences),
205
+ ("positive_torque", [r for r in differences if r["seed"] % 2 == 0]),
206
+ ("negative_torque", [r for r in differences if r["seed"] % 2]),
207
+ ):
208
+ values = [r["difference"] for r in selected]
209
+ summary[name] = {
210
+ "n": len(values),
211
+ "mean": statistics.mean(values),
212
+ "median": statistics.median(values),
213
+ "min": min(values),
214
+ "max": max(values),
215
+ "positive": sum(v > 0 for v in values),
216
+ "zero": values.count(0),
217
+ "negative": sum(v < 0 for v in values),
218
+ }
219
+ valid = all(all(row["checks"].values()) for row in integrity) and not git(
220
+ "status", "--porcelain"
221
+ )
222
+ save(
223
+ out / "result.json",
224
+ {
225
+ "valid": valid,
226
+ "status_after": git("status", "--porcelain"),
227
+ "paired_differences": differences,
228
+ "summary": summary,
229
+ "scientific_evidence": False,
230
+ },
231
+ )
232
+ save(out / "sha256sums.json", {f.name: sha(f) for f in sorted(out.iterdir())})
233
+ print(json.dumps({"valid": valid, "summary": summary}, indent=2))
234
+ return 0 if valid else 1
235
+
236
+
237
+ if __name__ == "__main__":
238
+ raise SystemExit(main())
scripts/run_stage1_reference_topology_mapping_r2.py CHANGED
@@ -6,9 +6,12 @@ from __future__ import annotations
6
  import hashlib
7
  import json
8
  import random
 
9
  from pathlib import Path
10
  from typing import Any
11
 
 
 
12
  from reference.stage1_topology_brian2 import runner_r2 as reference
13
  from scripts.run_stage1_topology_v2 import (
14
  canonical_coords,
@@ -90,7 +93,9 @@ def main() -> int:
90
  }
91
  OUT.parent.mkdir(parents=True, exist_ok=True)
92
  OUT.write_text(
93
- json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8"
 
 
94
  )
95
  print(
96
  json.dumps(
 
6
  import hashlib
7
  import json
8
  import random
9
+ import sys
10
  from pathlib import Path
11
  from typing import Any
12
 
13
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
14
+
15
  from reference.stage1_topology_brian2 import runner_r2 as reference
16
  from scripts.run_stage1_topology_v2 import (
17
  canonical_coords,
 
93
  }
94
  OUT.parent.mkdir(parents=True, exist_ok=True)
95
  OUT.write_text(
96
+ json.dumps(payload, indent=2, sort_keys=True) + "\n",
97
+ encoding="utf-8",
98
+ newline="\n",
99
  )
100
  print(
101
  json.dumps(
scripts/verify_determinism_reproduction.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Verify archived reproduction bytes without modifying or rerunning the study."""
2
+
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path
6
+
7
+
8
+ def main() -> None:
9
+ report = (
10
+ Path(__file__).resolve().parents[1]
11
+ / "research/reviews/DETERMINISM_REPRODUCTION_20261024"
12
+ )
13
+ artifacts = report / "artifacts"
14
+ checksums = json.loads((artifacts / "sha256sums.json").read_text(encoding="utf-8"))
15
+ for name, expected in checksums.items():
16
+ target = artifacts / name
17
+ if target.resolve().parent != artifacts.resolve():
18
+ raise ValueError(f"Invalid artifact path: {name}")
19
+ if hashlib.sha256(target.read_bytes()).hexdigest() != expected:
20
+ raise ValueError(f"Reproduction artifact changed: {name}")
21
+ freeze = json.loads((artifacts / "freeze.json").read_text(encoding="utf-8"))
22
+ actual = hashlib.sha256((report / "reproduce.py").read_bytes()).hexdigest()
23
+ if actual != freeze["harness_sha256"]:
24
+ raise ValueError("Executed reproduction harness changed")
25
+ print(f"PASS: {len(checksums)} immutable artifacts and executed harness")
26
+
27
+
28
+ if __name__ == "__main__":
29
+ main()
scripts/verify_emb001b_unseen_v4.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Verify archived V4 bytes and recompute the primary outcome from traces."""
2
+
3
+ import hashlib
4
+ import json
5
+ import math
6
+ import statistics
7
+ import subprocess
8
+ import zipfile
9
+ from pathlib import Path
10
+
11
+ ROOT = Path(__file__).resolve().parents[1]
12
+ OUT = ROOT / "research/experiments/EXP-EMB001B-UNSEEN-V4-20261024"
13
+
14
+
15
+ def main():
16
+ archive = json.loads((OUT / "archive-index.json").read_text())
17
+ assert (
18
+ hashlib.sha256((OUT / archive["archive"]).read_bytes()).hexdigest()
19
+ == archive["sha256"]
20
+ )
21
+ freeze = json.loads((OUT / "freeze.json").read_text())
22
+ git = ["git", "-c", f"safe.directory={ROOT.as_posix()}"]
23
+ for name, expected in freeze["hashes"].items():
24
+ name = name.replace("\\", "/")
25
+ blob = subprocess.check_output(
26
+ git + ["show", freeze["commit"] + ":" + name], cwd=ROOT
27
+ )
28
+ assert hashlib.sha256(blob).hexdigest() == expected, name
29
+ outcomes = json.loads((OUT / "outcomes.json").read_text())
30
+ assert len(outcomes) == 120
31
+ differences = []
32
+ with zipfile.ZipFile(OUT / archive["archive"]) as z:
33
+ for name, expected in json.loads((OUT / "sha256sums.json").read_text()).items():
34
+ data = (
35
+ z.read(name)
36
+ if name in archive["members"]
37
+ else (OUT / name).read_bytes()
38
+ )
39
+ assert hashlib.sha256(data).hexdigest() == expected, name
40
+ for seed in freeze["protocol"]["seeds"]:
41
+ raw = json.loads(z.read(f"runs-{seed}.json"))
42
+ assert set(raw["runs"]) == set(freeze["protocol"]["conditions"])
43
+ donor = raw["runs"]["donor_unperturbed"]
44
+ assert donor == json.loads(z.read(f"donor-{seed}.json"))
45
+ assert raw["schedule"] == [
46
+ (0.003 if seed % 2 == 0 else -0.003) if 250 <= t < 500 else 0.0
47
+ for t in range(1000)
48
+ ]
49
+ values = {}
50
+ for condition, run in raw["runs"].items():
51
+ trace = run["metrics"]["trace"]
52
+ assert run["error"] is None and len(trace) == 1000
53
+ assert run["before"] == donor["before"]
54
+ assert run["metrics"]["graph"] == donor["metrics"]["graph"]
55
+ assert (
56
+ run["metrics"]["gateway_state_before"]
57
+ == donor["metrics"]["gateway_state_before"]
58
+ )
59
+ value = math.sqrt(
60
+ statistics.mean(
61
+ (r["target_rad"] - r["q_rad"]) ** 2 for r in trace[250:]
62
+ )
63
+ )
64
+ row = next(
65
+ r
66
+ for r in outcomes
67
+ if r["seed"] == seed and r["condition"] == condition
68
+ )
69
+ assert value == row["post_disturbance_rmse"]
70
+ values[condition] = value
71
+ yoke = raw["runs"]["yoked_challenge"]
72
+ assert (
73
+ raw["runs"]["yoked_unperturbed"]["metrics"]["trace"]
74
+ == donor["metrics"]["trace"]
75
+ )
76
+ assert [
77
+ [r["observed_q_rad"], r["observed_omega_rad_s"]]
78
+ for r in yoke["metrics"]["trace"]
79
+ ] == donor["sensor_tape"]
80
+ assert yoke["motor_tape"] == donor["motor_tape"]
81
+ assert yoke["sensor_tape"] != donor["sensor_tape"]
82
+ assert all(
83
+ r["observed_q_rad"] == r["observed_omega_rad_s"] == 0
84
+ for r in raw["runs"]["feedback_absent_challenge"]["metrics"]["trace"]
85
+ )
86
+ differences.append(
87
+ {
88
+ "seed": seed,
89
+ "difference": values["yoked_challenge"]
90
+ - values["closed_challenge"],
91
+ }
92
+ )
93
+ result = json.loads((OUT / "result.json").read_text())
94
+ assert result["valid"] and result["status_after"] == ""
95
+ assert differences == result["paired_differences"]
96
+ assert (
97
+ statistics.mean(r["difference"] for r in differences)
98
+ == result["summary"]["pooled"]["mean"]
99
+ )
100
+ print(
101
+ "PASS: source bindings, archived hashes, 120 raw runs, causal controls and paired primary outcomes"
102
+ )
103
+
104
+
105
+ if __name__ == "__main__":
106
+ main()
scripts/verify_emb002a_delay_sweep_v4.py CHANGED
@@ -5,6 +5,8 @@ from __future__ import annotations
5
 
6
  import hashlib
7
  import json
 
 
8
  from pathlib import Path
9
  from typing import Any
10
 
@@ -100,9 +102,16 @@ def main() -> int:
100
  assert hashes["preregistration"] == sha256(PREREG)
101
  assert hashes["evaluation_data"] == sha256(OUT / "data/evaluation.json")
102
  assert hashes["statistics"] == sha256(OUT / "analysis/statistics.json")
103
- assert hashes["canonical_embodiment_source"] == sha256(
104
- ROOT / "src/research/connectome_embodiment.py"
105
- )
 
 
 
 
 
 
 
106
 
107
  for line in (OUT / "checksums.sha256").read_text(encoding="utf-8").splitlines():
108
  digest, relpath = line.split(" ", 1)
 
5
 
6
  import hashlib
7
  import json
8
+ import re
9
+ import subprocess
10
  from pathlib import Path
11
  from typing import Any
12
 
 
102
  assert hashes["preregistration"] == sha256(PREREG)
103
  assert hashes["evaluation_data"] == sha256(OUT / "data/evaluation.json")
104
  assert hashes["statistics"] == sha256(OUT / "analysis/statistics.json")
105
+ # Persisted DATA bind the executed commit, not later runtime development.
106
+ source_commit = manifest["source_freeze"]["commit"]
107
+ assert re.fullmatch(r"[0-9a-f]{40}", source_commit)
108
+ source = subprocess.run(
109
+ ["git", "show", f"{source_commit}:src/research/connectome_embodiment.py"],
110
+ cwd=ROOT,
111
+ check=True,
112
+ capture_output=True,
113
+ ).stdout
114
+ assert hashes["canonical_embodiment_source"] == hashlib.sha256(source).hexdigest()
115
 
116
  for line in (OUT / "checksums.sha256").read_text(encoding="utf-8").splitlines():
117
  digest, relpath = line.split(" ", 1)
scripts/verify_stage1_reference_dynamics_reuse_r2.py CHANGED
@@ -120,7 +120,9 @@ def main() -> int:
120
  }
121
  OUT.parent.mkdir(parents=True, exist_ok=True)
122
  OUT.write_text(
123
- json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8"
 
 
124
  )
125
  print(
126
  json.dumps(
 
120
  }
121
  OUT.parent.mkdir(parents=True, exist_ok=True)
122
  OUT.write_text(
123
+ json.dumps(payload, indent=2, sort_keys=True) + "\n",
124
+ encoding="utf-8",
125
+ newline="\n",
126
  )
127
  print(
128
  json.dumps(