diff --git a/.gitattributes b/.gitattributes index 7d134b81..bb009874 100644 --- a/.gitattributes +++ b/.gitattributes @@ -104,3 +104,4 @@ harness/experiments/default-fidelity-20260913/** -text whitespace=cr-at-eol harness/experiments/default-fidelity-20260913/**/*.log -whitespace harness/experiments/default-production-20260913/** -text whitespace=cr-at-eol harness/experiments/default-followup-20260913/** -text whitespace=cr-at-eol +harness/experiments/default-consumers-20260913/** -text whitespace=cr-at-eol diff --git a/crates/oa-server/tests/http/port_anchors.rs b/crates/oa-server/tests/http/port_anchors.rs index 33c42456..9d9b017a 100644 --- a/crates/oa-server/tests/http/port_anchors.rs +++ b/crates/oa-server/tests/http/port_anchors.rs @@ -75,6 +75,164 @@ fn assert_pair(current: &StoredProject) { assert_eq!(compiled.bundle, current.source_project); } +#[tokio::test] +async fn explicit_empty_port_anchors_survive_http_move_and_restart_but_export_compactly() { + fn replace_once(source: &mut String, before: &str, after: &str) { + assert_eq!( + source.matches(before).count(), + 1, + "ambiguous or absent anchor: {before}" + ); + let replacement = source.replacen(before, after, 1); + assert_ne!(*source, replacement, "fixture mutation must change source"); + *source = replacement; + } + + async fn assert_exports(server: &TestServer, current: &StoredProject, node: Id) -> String { + let response = server + .client + .get(server.url(&format!("/projects/{}/source-project", current.resource_id))) + .send() + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.headers()["etag"], current.etag()); + assert_eq!( + response.json::().await.unwrap(), + current.source_project + ); + + let response = server + .client + .get(server.url(&format!("/projects/{}/source", current.resource_id))) + .send() + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let canonical = response.text().await.unwrap(); + assert_eq!(oa_core::language::parse(&canonical).unwrap(), current.model); + assert_eq!(canonical, oa_core::language::format(¤t.model)); + let exported = compile_bundle( + &SourceBundle { + entry: "project.arcadia".into(), + files: BTreeMap::from([("project.arcadia".into(), canonical.clone())]), + }, + ProjectLimits::default(), + ) + .unwrap(); + assert_eq!(exported.decoded.project, current.model); + assert!(exported.index.declarations.contains_key(&node)); + assert!( + !exported + .index + .fields + .contains_key(&(node, "port_anchors".into())) + ); + canonical + } + + let mut authored = source_bundle(); + replace_once( + authored.files.get_mut("views.arcadia").unwrap(), + "diagram @architecture; element @vessel;", + "diagram @architecture; element @vessel;\n position { x 12.5; y -4.0; };\n port_anchors [] ; // Keep explicit automatic placement.\n", + ); + let compiled = compile_bundle(&authored, ProjectLimits::default()).unwrap(); + assert_eq!( + compiled.bundle, authored, + "the HTTP fixture must already be stable" + ); + let diagram = id(&compiled.decoded.project, "architecture"); + let node = id(&compiled.decoded.project, "actor_view"); + assert!( + compiled + .index + .fields + .contains_key(&(node, "port_anchors".into())) + ); + assert_eq!( + compiled.decoded.project.diagrams[&diagram].nodes[&node].position, + oa_core::Position { x: 12.5, y: -4.0 } + ); + assert!( + compiled.decoded.project.diagrams[&diagram].nodes[&node] + .port_anchors + .is_empty() + ); + + // Independent full-model oracle: only this placement coordinate changes. + let mut expected_model = compiled.decoded.project.clone(); + expected_model + .diagrams + .get_mut(&diagram) + .unwrap() + .nodes + .get_mut(&node) + .unwrap() + .position + .x = 25.0; + let mut expected_source = authored.clone(); + replace_once( + expected_source.files.get_mut("views.arcadia").unwrap(), + "x 12.5;", + "x 25.0;", + ); + + let directory = tempfile::tempdir().unwrap(); + let server = TestServer::start(directory.path(), None).await; + let response = server + .client + .post(server.url("/projects/import-source-project")) + .json(&authored) + .send() + .await + .unwrap(); + let status = response.status(); + let value: Value = response.json().await.unwrap(); + assert_eq!(status, StatusCode::CREATED, "{value}"); + let original = server + .get(value["resource_id"].as_str().unwrap().parse().unwrap()) + .await; + assert_eq!(original.model, compiled.decoded.project); + assert_eq!(original.source_project, authored); + assert_pair(&original); + assert_exports(&server, &original, node).await; + + let response = transact( + &server, + &original, + json!([{"MoveNode": {"diagram": diagram, "node": node, + "position": {"x": 25.0, "y": -4.0}, "with_descendants": false}}]), + false, + ) + .await; + let status = response.status(); + let value: Value = response.json().await.unwrap(); + assert_eq!(status, StatusCode::OK, "{value}"); + assert_eq!(value["changed"], true); + let current = server.get(original.resource_id).await; + assert_eq!(current.revision, original.revision + 1); + assert_eq!(current.model, expected_model); + // Exact bundle equality covers the explicit empty field, its rationale, + // sibling placement, and all untouched files and source trivia. + assert_eq!(current.source_project, expected_source); + assert_pair(¤t); + let canonical = assert_exports(&server, ¤t, node).await; + + server.stop().await; + let server = TestServer::start(directory.path(), None).await; + let reopened = server.get(current.resource_id).await; + assert_eq!(reopened.model, expected_model); + assert_eq!(reopened.source_project, expected_source); + assert_eq!( + serde_json::to_value(&reopened).unwrap(), + serde_json::to_value(¤t).unwrap() + ); + assert_pair(&reopened); + assert_eq!(assert_exports(&server, &reopened, node).await, canonical); + server.stop().await; +} + #[tokio::test] async fn port_anchor_http_commands_and_source_edits_preserve_per_view_state_after_restart() { let directory = tempfile::tempdir().unwrap(); diff --git a/harness/experiments/default-consumers-20260913/README.md b/harness/experiments/default-consumers-20260913/README.md new file mode 100644 index 00000000..cb6308f6 --- /dev/null +++ b/harness/experiments/default-consumers-20260913/README.md @@ -0,0 +1,62 @@ +# Consumer, desktop, lint and Windows build verification + +Four closed gates captured the same 613 input files with HEAD +`df2e1cd65ca5e56f47669e04a9d4bcb426900fcd` plus the locally added, uncommitted +HTTP witness in `crates/oa-server/tests/http/port_anchors.rs`. That file is the +only changed captured input relative to the preceding core follow-up run. Each +gate confirms its inputs stayed unchanged during execution. The source ZIP and +input pins identify the executed candidate; HEAD alone does not contain the new +HTTP witness: + +| Capture | Executed scope | Result | +| --- | --- | --- | +| `production-consumers-1` | CLI `source_cli` and server `http` integration targets | 12 CLI + 51 HTTP tests passed; none failed or ignored | +| `production-clippy-1` | Workspace, all targets, `-D warnings` | Exit 0 | +| `production-desktop-1` | `oa-desktop` / `arcadia-studio` automated tests | 894 passed, 0 failed, 6 ignored | +| `production-windows-build-1` | Workspace binaries | Exit 0; CLI, server and Studio compiler receipts | + +All runs used native Windows gnullvm Rust/Cargo 1.98.1, locked offline +dependencies, one Cargo job and incremental compilation off. UTC intervals on +2026-09-13 were 11:51:14–11:56:49, 11:57:30–12:02:34, +12:03:15–12:10:24 and 12:10:46–12:13:19, respectively. Complete commands, +environment, timings and raw streams are retained. + +The six ignored desktop tests are opt-in rename/layout/symbol-navigation timing, +two supported-font cluster/bidirectional-text witnesses, and diagnostic +projection performance measurements. Their exact names and reasons remain in +the raw log; the verifier checks their names. Passing automated desktop tests +does not establish an interactive native workflow result. Partial native trial +1 is intentionally outside this archive. + +`evidence.zip` preserves the four original start/result/stdout/stderr sets and +the original `gate-policy.py` producer. Original source payloads were compared +byte-for-byte across all four gate directories before packaging. Identical +`inputs.json` and `inputs.zip` are stored once as `shared/inputs.json` and +`shared/inputs.zip`; each gate's independent receipts still pin these bytes: + +- Manifest: `6bf74c1f68431a034681b754617c39d387783f71354c9106649aec9480e4f29f` +- Source ZIP: `e20f33dc363850a9289103a7be28ad613c3fffb2039061957a0186cac72d541b` + +The internal `capture-files.json` pins all 19 physical payloads. Outer +`files.json` pins every other package file; its publishing Git commit identifies +the outer manifest. No current-source inventory or new execution claim was +created while packaging these closed captures. + +Run with Python 3.10 or newer from this directory: + +```text +python -B verify.py +``` + +This reads only. It checks memberships and duplicate ZIP entries; all file and +input hashes; fixed commit/helper/source pins; each gate's source references; +start/result and timestamp consistency; exact commands; named test outcomes, +per-suite counts and ignored names; and compiler-artifact receipts against raw +Cargo records. It does not extract files, run Cargo/tests, read current sources, +or look up executable files on the machine. Producer-host paths in the archived +runner require adaptation for a new capture. + +No binaries are distributed. Compiler-artifact hashes are historical readback +receipts, not independently reverified executable files in this package. These +records establish the stated Windows automated gates, with no interactive +native, Linux, package-launch or release-publication acceptance claim. diff --git a/harness/experiments/default-consumers-20260913/evidence.zip b/harness/experiments/default-consumers-20260913/evidence.zip new file mode 100644 index 00000000..ad4595f8 Binary files /dev/null and b/harness/experiments/default-consumers-20260913/evidence.zip differ diff --git a/harness/experiments/default-consumers-20260913/files.json b/harness/experiments/default-consumers-20260913/files.json new file mode 100644 index 00000000..6e695623 --- /dev/null +++ b/harness/experiments/default-consumers-20260913/files.json @@ -0,0 +1,14 @@ +{ + "evidence.zip": { + "bytes": 2546955, + "sha256": "43d3483eab331b98516d2ab8969a634c7c714fb827df1acfe972dd094923a9fb" + }, + "README.md": { + "bytes": 3539, + "sha256": "8651979c51ee6a69f059a2be2691fdc814ca00b8118e9786223343e14a26c8eb" + }, + "verify.py": { + "bytes": 10482, + "sha256": "2b761d6c4182489a532898f067ed8fcfbce64009cdf346a88bcc70e00f22770b" + } +} diff --git a/harness/experiments/default-consumers-20260913/verify.py b/harness/experiments/default-consumers-20260913/verify.py new file mode 100644 index 00000000..2ae9a2be --- /dev/null +++ b/harness/experiments/default-consumers-20260913/verify.py @@ -0,0 +1,159 @@ +"""Read-only verification of the four frozen consumer/lint/desktop/build captures.""" +from pathlib import Path, PurePosixPath +from datetime import datetime +import hashlib +import io +import json +import math +import re +import zipfile + +ROOT = Path(__file__).resolve().parent +HEAD = "df2e1cd65ca5e56f47669e04a9d4bcb426900fcd" +INPUTS = {"bytes": 102567, "sha256": "6bf74c1f68431a034681b754617c39d387783f71354c9106649aec9480e4f29f"} +SOURCE_ZIP = {"bytes": 2483742, "sha256": "e20f33dc363850a9289103a7be28ad613c3fffb2039061957a0186cac72d541b"} +HELPER = {"bytes": 3097, "sha256": "f14928bb472fc160e0a100a98139cf537380ec232626b0a7bb66f72bfc06128b"} +GATES = { + "production-consumers-1": (63, 0, 0, 4), + "production-clippy-1": (0, 0, 0, 0), + "production-desktop-1": (894, 0, 6, 1), + "production-windows-build-1": (0, 0, 0, 3), +} +GATE_FILES = {"started.json", "result.json", "stdout.log", "stderr.log"} +SHARED_FILES = {"inputs.json", "inputs.zip"} +COMMANDS = { + "production-consumers-1": ["cargo", "test", "--locked", "--offline", "-p", "oa-server", "--test", "http", "-p", "oa-cli", "--test", "source_cli", "--no-fail-fast", "--message-format=json", "--", "--nocapture"], + "production-clippy-1": ["cargo", "clippy", "--locked", "--offline", "--workspace", "--all-targets", "--message-format=json", "--", "-D", "warnings"], + "production-desktop-1": ["cargo", "test", "--locked", "--offline", "-p", "oa-desktop", "--bin", "arcadia-studio", "--no-fail-fast", "--message-format=json", "--", "--nocapture"], + "production-windows-build-1": ["cargo", "build", "--locked", "--offline", "--workspace", "--bins", "--message-format=json"], +} +ROWS = { + "production-consumers-1": [(12, 0, 0, 0, 0), (51, 0, 0, 0, 0)], + "production-clippy-1": [], + "production-desktop-1": [(894, 0, 6, 0, 0)], + "production-windows-build-1": [], +} +TARGETS = { + "production-consumers-1": {"http", "source_cli", "arcadia", "arcadia-server"}, + "production-clippy-1": set(), + "production-desktop-1": {"arcadia-studio"}, + "production-windows-build-1": {"arcadia", "arcadia-server", "arcadia-studio"}, +} +IGNORED = { + "app::source_rename::timing_tests::accepted_release1_prepare_commit_sync_and_confirm_timing", + "app::source_rename::timing_tests::layout_timing_tests::accepted_release1_constructor_vs_reuse_through_next_highlight", + "app::source_symbols::tests::source_symbols_100_modules_1000_definitions_10000_uses", + "source_annotations::anchor_tests::final_multi_glyph_cluster_anchor_uses_logical_trailing_advance_edge", + "source_annotations::anchor_tests::mixed_direction_omitted_separator_has_one_logical_predecessor_anchor", + "source_annotations::performance_tests::annotation_projection_cost_distributions", +} + + +def require(condition, message): + if not condition: + raise SystemExit(message) + + +def digest(data): + return {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()} + + +def membership(archive, expected, label): + names = archive.namelist() + require(len(names) == len(set(names)), f"{label}: duplicate ZIP members") + require(set(names) == set(expected), f"{label}: ZIP membership mismatch") + require(all(not PurePosixPath(name).is_absolute() and ".." not in PurePosixPath(name).parts and "\\" not in name for name in names), f"{label}: unsafe member name") + + +manifest = json.loads((ROOT / "files.json").read_text(encoding="utf-8")) +actual = {path.relative_to(ROOT).as_posix() for path in ROOT.rglob("*") if path.is_file() and path.name != "files.json" and "__pycache__" not in path.parts} +require(actual == set(manifest) == {"README.md", "verify.py", "evidence.zip"}, "Outer membership mismatch") +for name, expected in manifest.items(): + require(digest((ROOT / name).read_bytes()) == expected, f"Outer hash mismatch: {name}") + +with zipfile.ZipFile(ROOT / "evidence.zip") as evidence: + def read_json(name): + return json.loads(evidence.read(name)) + + def read_gate(gate, name): + # Original source payloads have identical byte pins in all four receipts. + # Store them once; independently check each gate against those bytes. + return evidence.read(f"shared/{name}" if name in SHARED_FILES else f"{gate}/{name}") + + payloads = read_json("capture-files.json") + expected_members = {f"{gate}/{name}" for gate in GATES for name in GATE_FILES} | {"gate-policy.py"} | {f"shared/{name}" for name in SHARED_FILES} + require(set(payloads) == expected_members, "Captured file inventory mismatch") + membership(evidence, expected_members | {"capture-files.json"}, "Evidence") + for name, expected in payloads.items(): + require(digest(evidence.read(name)) == expected, f"Capture hash mismatch: {name}") + require(digest(evidence.read("gate-policy.py")) == HELPER, "Frozen helper pin mismatch") + + snapshots = [] + times = [] + for gate, (passed, failed, ignored, artifact_count) in GATES.items(): + result = read_json(f"{gate}/result.json") + started = read_json(f"{gate}/started.json") + require(all(key in result and result[key] == value for key, value in started.items()), f"{gate}: start/result mismatch") + require(result["head"] == HEAD and result["cwd"] == "C:\\ar-default-9e8r6t", f"{gate}: source identity mismatch") + require(result["exitCode"] == 0 and result["inputsUnchanged"] is True, f"{gate}: failed or changed input receipt") + require(result["inputs"] == INPUTS and result["archive"] == SOURCE_ZIP and result["helper"] == HELPER, f"{gate}: frozen pins mismatch") + for key, name in [("inputs", "inputs.json"), ("archive", "inputs.zip"), ("stdout", "stdout.log"), ("stderr", "stderr.log")]: + require(digest(read_gate(gate, name)) == result[key], f"{gate}: {key} receipt mismatch") + + start, finish = (datetime.fromisoformat(result[key]) for key in ["start", "finish"]) + duration = result["seconds"] + require(start.tzinfo is not None and finish.tzinfo is not None, f"{gate}: unzoned timestamps") + require(math.isfinite(duration) and duration >= 0 and 0 <= (finish - start).total_seconds() - duration < 30, f"{gate}: time consistency") + times.append((start, finish)) + environment = result["environment"] + require(environment["RUSTUP_TOOLCHAIN"] == "stable-x86_64-pc-windows-gnullvm" and environment["CARGO_INCREMENTAL"] == "0", f"{gate}: native Windows environment mismatch") + require(result["cargo"] == "cargo 1.98.1 (797e8a9bc 2026-08-05)" and result["rustc"] == "rustc 1.98.1 (48a229cea 2026-09-01)", f"{gate}: tool versions mismatch") + require(environment["CARGO_BUILD_JOBS"] == "1", f"{gate}: jobs receipt mismatch") + require(result["command"] == COMMANDS[gate], f"{gate}: command mismatch") + + inputs = json.loads(read_gate(gate, "inputs.json")) + require(len(inputs) == result["inputCount"] == 613, f"{gate}: input count mismatch") + require({"crates/oa-core/src/source_defaults.rs", "crates/oa-core/src/source_defaults_tests.rs", "crates/oa-server/tests/http.rs", "crates/oa-cli/tests/source_cli.rs"} <= set(inputs), f"{gate}: production inputs missing") + with zipfile.ZipFile(io.BytesIO(read_gate(gate, "inputs.zip"))) as sources: + membership(sources, inputs, f"{gate} source") + for name, expected in inputs.items(): + require(digest(sources.read(name)) == expected, f"{gate}: source hash mismatch: {name}") + snapshots.append(inputs) + + stdout = evidence.read(f"{gate}/stdout.log").decode("utf-8") + rows = [tuple(map(int, row)) for row in re.findall(r"^test result: ok\. (\d+) passed; (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out;", stdout, re.M)] + require(rows == ROWS[gate], f"{gate}: per-suite outcomes mismatch") + totals = tuple(sum(row[index] for row in rows) for index in range(5)) + require(totals == (passed, failed, ignored, 0, 0), f"{gate}: raw summary totals mismatch: {totals}") + runs = [int(count) for count in re.findall(r"^running (\d+) tests?$", stdout, re.M)] + require(runs == [sum(row[:4]) for row in rows], f"{gate}: suite sizes mismatch") + named = re.findall(r"^test (.+?) \.\.\. (ok|FAILED|ignored(?:, [^\r\n]*)?)\r?$", stdout, re.M) + named_totals = (sum(status == "ok" for _, status in named), sum(status == "FAILED" for _, status in named), sum(status.startswith("ignored") for _, status in named)) + require(named_totals == (passed, failed, ignored), f"{gate}: raw named totals mismatch: {named_totals}") + if ignored: + require({name for name, status in named if status.startswith("ignored")} == IGNORED, f"{gate}: ignored test inventory mismatch") + artifacts, finished = [], [] + for line in stdout.splitlines(): + try: + item = json.loads(line) + except json.JSONDecodeError: + continue + if not isinstance(item, dict): + continue + if item.get("reason") == "compiler-artifact" and item.get("executable"): + artifacts.append(item) + if item.get("reason") == "build-finished": + finished.append(item.get("success")) + require(finished == [True], f"{gate}: successful build record mismatch") + require(len(artifacts) == len(result["artifacts"]) == artifact_count and artifacts == [entry["cargo"] for entry in result["artifacts"]], f"{gate}: compiler receipt mismatch") + require({item["target"]["name"] for item in artifacts} == TARGETS[gate], f"{gate}: executable targets mismatch") + for entry in result["artifacts"]: + receipt = entry["readback"] + require(isinstance(receipt, dict) and receipt.get("bytes", 0) > 0 and re.fullmatch(r"[0-9a-f]{64}", receipt.get("sha256", "")) is not None, f"{gate}: invalid historical binary receipt") + + require(all(snapshot == snapshots[0] for snapshot in snapshots), "Gate inputs differ") + require(all(before[1] <= after[0] for before, after in zip(times, times[1:])), "Gate chronology mismatch") + +print("Verified 19 captured payloads; four independently pinned, identical 613-input snapshots captured with HEAD " + HEAD + ".") +print("Windows: consumers 63 passed (12 CLI + 51 HTTP); desktop 894 passed / 6 ignored; strict workspace Clippy and 3-binary build succeeded.") +print("Recorded automated evidence only; no binaries distributed and no interactive native, Linux, packaging or release acceptance claim.") diff --git a/harness/text-default-omission-consumers-20260913.md b/harness/text-default-omission-consumers-20260913.md new file mode 100644 index 00000000..acedfc43 --- /dev/null +++ b/harness/text-default-omission-consumers-20260913.md @@ -0,0 +1,40 @@ +# Ordinary default omission: public consumer verification + +The ordinary source changes at `7bb366f66a8c6f8b6f66fc9b04c9e3b39f84b78e` now pass the CLI, HTTP and desktop automated consumer gates, strict workspace Clippy, and a matching Windows build. A new HTTP regression test verifies the distinction between preserving authored source and producing compact canonical export. Native workflow acceptance and integration remain incomplete. + +## Executed candidate and results + +All four runs captured HEAD `df2e1cd65ca5e56f47669e04a9d4bcb426900fcd` plus the new test in `crates/oa-server/tests/http/port_anchors.rs`. That file is the only captured input differing from the [previous full-core run](text-default-omission-followup-verified-20260913.md); production code is unchanged. These are execution-input pins, not a claim that HEAD alone includes the new test. + +| Gate | Result | UTC interval, September 13, 2026 | +| --- | --- | --- | +| `production-consumers-1` | 12 CLI and 51 HTTP tests passed; no failures or ignored tests | 11:51:14–11:56:49 | +| `production-clippy-1` | Workspace, all targets, `-D warnings`: exit 0 | 11:57:30–12:02:34 | +| `production-desktop-1` | 894 passed, 0 failed, 6 ignored | 12:03:15–12:10:24 | +| `production-windows-build-1` | Desktop, CLI and server binaries built; exit 0 | 12:10:46–12:13:19 | + +Each run recorded 613 unchanged inputs, manifest SHA-256 `6bf74c1f68431a034681b754617c39d387783f71354c9106649aec9480e4f29f` and source ZIP SHA-256 `e20f33dc363850a9289103a7be28ad613c3fffb2039061957a0186cac72d541b`. The [portable archive](experiments/default-consumers-20260913/README.md) contains original inputs, raw streams, start/end receipts and the capture helper. It records executable hashes but does not distribute runnable binaries or establish release acceptance. + +The new HTTP witness imports explicit `port_anchors []` with deliberate spacing and a rationale comment, then moves one placement through an actual HTTP transaction. Its expected complete model is independently constructed by changing that coordinate, and its expected source bundle changes exactly one numeric token. It checks revision progression, authored bundle retrieval, canonical model round trip and absence of the default field in canonical export. Server restart must preserve the complete stored response and both export forms. The witness passed. + +Existing desktop tests also passed graphical creation with one persisted UUID and the two-file Apply, graphical edit, exact Undo, Save As and reopen workflow. They are automated application checks; native interaction remains a separate gate. + +The two HTTP stderr messages reporting Windows access denied come from deliberate failed atomic-replacement tests: `failed_atomic_replacement_preserves_the_accepted_model` and `source_projects_failed_atomic_comment_write_preserves_both_representations`. Both replace an isolated storage file with a directory, expect HTTP 503 and unchanged accepted state, then restore it. Both passed; these messages do not indicate a failed consumer run. + +Six existing desktop probes remain ignored: the two accepted-release timing probes, the large source-symbol probe, two font-cluster/direction probes, and `annotation_projection_cost_distributions`. Their exact names are retained in raw output. Incidental chooser timings in stderr are not controlled evidence of a performance improvement or regression in source omission. + +Independent read-only review checked the report against raw outcomes, the sole HTTP input difference, the expected storage refusals and the final archive/verifier. Review identified imprecise HEAD-only wording in the archive README; it was corrected to name the captured HTTP addition. No substantive finding remained. Parent readback verified all 613 executed source inputs and four archive files against the staged Git blobs; the archive verifier, targeted HTTP-file rustfmt check and staged whitespace check passed. The evidence ZIP SHA-256 is `43d3483eab331b98516d2ab8969a634c7c714fb827df1acfe972dd094923a9fb`. + +## Reproduction and remaining acceptance + +Environment: native Windows, Rust/Cargo 1.98.1, `stable-x86_64-pc-windows-gnullvm`, LLVM-MinGW 20260826 UCRT, locked offline dependencies, `CARGO_BUILD_JOBS=1`, `CARGO_INCREMENTAL=0`. Use the established project Windows toolchain environment setup. + +```text +cargo test --locked --offline -p oa-server --test http -p oa-cli --test source_cli --no-fail-fast --message-format=json -- --nocapture +cargo clippy --locked --offline --workspace --all-targets --message-format=json -- -D warnings +cargo test --locked --offline -p oa-desktop --bin arcadia-studio --no-fail-fast --message-format=json -- --nocapture +cargo build --locked --offline --workspace --bins --message-format=json +python harness/experiments/default-consumers-20260913/verify.py +``` + +Native drag, exact model Undo/Redo, saved-source preservation and reopen still require a completed observed workflow against the matching build. No earlier diagnostic GUI run substitutes for it. Source8 composition and combined verification remain under the ownership partition in Gitea issues 7 and 11. Linux checks remain user-paused. This checkpoint does not close issue 11, deliver a new release, or establish the full OA/SA objective.