Record Scenario planner verification and adapter design boundaries
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# Scenario sibling-block planner: Windows foundation verification
|
||||
|
||||
This is the post-prerelease foundation for [issue 29](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/29), owned by Codex-9e8r6t /root on `codex/scenario-multiselect-9e8r6t-20260924`. It is excluded from the current issue 37 prerelease. No Scenario group canvas/App adapter or aggregate removal is delivered by this milestone.
|
||||
|
||||
## Source and behavior
|
||||
|
||||
Tested source **7d3d7b23e768b66b93b608bcc6b4fb1e7f972f89** adds the planner on integrated base **8ddfadf440b5c208b1adb6b211a97ba0c99cff04**. The original local port `d79a5ed37b2b64ac81d90137275fad8a97067418` used base `897d9f04`; its first compilation failed on the already identified upstream missing `text_editor` test import. Rebasing the unpublished contribution onto the owner's corrected base brought in that import. The failed run remains preserved and establishes no executed-test result.
|
||||
|
||||
`behavior/scenario/group_reorder.rs` admits the entire exact typed set under one actual immediate Scenario or Branch parent, validates complete edited-list integrity and structural reachability, then freezes a validated complete Project. It preserves semantic list order within the selected block, regardless of selection order. A selected destination anchor or identical result is a no-op only after admission. Other proposals are fully validated before returning one `SetSteps` command. Unsupported mixtures, different parents and invalid temporal/lifecycle outcomes refuse without pruning or dependency expansion. Advisory authoring warnings remain allowed.
|
||||
|
||||
The existing singleton `scenario::reorder` uses the same planner while retaining its Command-returning interface. The UI's existing singleton feedback is not claimed to distinguish a no-op; the future group adapter must do so. Saved presentation records, all other sequences, references and metadata remain untouched by the semantic operation. [Policy and adapter decision boundary](scenario-group-reorder-policy-20260924.md) define the exact scope.
|
||||
|
||||
## Executed Windows checks
|
||||
|
||||
Environment: Rust/Cargo 1.98.1, `stable-x86_64-pc-windows-gnullvm`, LLVM-MinGW 20260826; one build job and incremental compilation disabled. Commands run from the named worktree with the configured toolchain environment. No Linux/WSL build or check was run.
|
||||
|
||||
| Command | Outcome |
|
||||
| --- | --- |
|
||||
| `cargo test -p oa-desktop --bin arcadia-studio --message-format=json group_reorder -- --test-threads=1` | 20 passed, 0 failed: all 12 new Scenario cases and eight existing Process cases. |
|
||||
| `cargo test -p oa-desktop --bin arcadia-studio --message-format=json scenario -- --test-threads=1` | 250 passed, 0 failed, 1 existing ignored diagnostic timing test. |
|
||||
| `cargo fmt --all -- --check` | Exit 0. |
|
||||
| `cargo clippy -p oa-desktop --all-targets --message-format=json -- -D warnings` | Exit 0; warnings denied. |
|
||||
|
||||
These filters overlap; their counts are not additive. The ignored case is `behavior::scenario::spacing::controller::timing_tests::saved_spacing_capture_prepare_20_lanes_1000_messages_3_views`, explicitly an opt-in diagnostic without a latency acceptance threshold.
|
||||
|
||||
The 12 new evaluators cover OA and SA root/operand stable-block order; exact selected-anchor and unchanged no-ops with redo; actual singleton Document no-op; malformed complete lists, wrong target types and omitted Branch reachability; unlisted warning children; explicit call/reply pairs; nested Fragment reply and lifetime failures; opaque local ScenarioUse bindings; conditional Delete; ALT exclusivity versus invalid PAR baseline; repeated-reply warnings; and two nonempty affected saved layouts plus an unrelated saved view. Literal whole-Project expectations are independent of planner output. Successful cases check full execution, Document state, source format/parse, saved bundle reopen and one exact Undo/Redo.
|
||||
|
||||
## Evidence and review
|
||||
|
||||
Closed raw runs, hashes, commands, captured source inputs, original helper bytes and exact Git-blob comparison receipts are packaged in [the evidence directory](scenario-group-planner-evidence-20260924/checks/README.md). Each run captures 804 relevant tracked/visible inputs, including the 71 patched vendor files and explicit compile-time fixtures; successful final runs retain the same exact input map. The gate checks before/after equality, and separate source proofs compare the map with the named commit. This is identified local build evidence, not a claim of a hermetic or independently reproduced compiler environment.
|
||||
|
||||
The archive is **6,416,449 bytes**, SHA-256 **211530e47aec6254ee1476081352139497091aae6fef249274c20aa1808b741f**. Its manifest preserves five closed runs (one failed, four successful), five source proofs, 43 logical files and 37 stored members across two distinct source capsules. Offline readback passes. The retained [preparation selfcheck transcript](scenario-group-planner-evidence-20260924/selfcheck-1.log) confirms exact original raw/helper/proof bytes, existing-output refusal and 11 corruption-rejection checks. `checks/selfcheck.py` is the preparation utility; it requires the original raw-run directory and is separate from the pinned offline package inventory.
|
||||
|
||||
The [independent planner source review](scenario-group-planner-evidence-20260924/reviews/planner-review.md) found no material defect and requested discriminating fixtures and a singleton no-op check. Its follow-up inspected the expanded 12 cases; the parent then executed the checks above. That review retains its original inspected-base and line-reference boundaries.
|
||||
|
||||
The final [independent delivery review](scenario-group-planner-evidence-20260924/reviews/delivery-review.md) found no material discrepancy in the closed results, executed names, claims or assembled evidence. Its offline verification passed, and all 40 archived raw/proof records matched the original captures exactly. This review does not independently rerun the compiler or accept remote publication. Original review bytes are retained through scoped Git attributes; the parent's final staging check compares every delivery file with its exact Git index bytes.
|
||||
|
||||
The [adapter mapping](scenario-group-planner-evidence-20260924/reviews/adapter-design.md) and [fresh design critique](scenario-group-planner-evidence-20260924/reviews/adapter-critique.md) are proposals/review, not implemented behavior. Parent synthesis accepts full Scenario event envelopes with representation-specific selection scopes. The saved-view gesture remains unresolved: exact authored Y coordinates can leave arrows in their prior positions after semantic reorder. Compare a group handle and an explicit operation-local order preview before accepting the proposed arm. Gesture retirement, selection ABA, saved spacing precedence, actual visibility, raw legacy controls and observable semantic success need adapter verification.
|
||||
|
||||
There is no full-workspace, native group-gesture, packaged application or release acceptance for this contribution. Existing prerelease source remains frozen independently. Live integration and subsequent work stay in issue 29, while issue 37 owns the current Windows package and separate-machine QA.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Preserve original independent review bytes for provenance.
|
||||
reviews/*.md -text whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol
|
||||
selfcheck-1.log -text whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol
|
||||
@@ -0,0 +1,14 @@
|
||||
# Scenario group planner captured checks
|
||||
|
||||
These tools package explicitly named, completed runs from `CHECKOUT/.local/scenario-group-gates` without executing archived source or running Cargo. Adapted only in input-directory and manifest-kind names from the published prerelease Connection/Route evidence tools at `93d2a6f9af40061bf92cd81ba33d5648d0181cb1`.
|
||||
|
||||
```powershell
|
||||
python -B prepare.py --root CHECKOUT --runs planner-focused-1 planner-focused-2 format-1 --output NEW_DIRECTORY
|
||||
python -B NEW_DIRECTORY/verify.py
|
||||
```
|
||||
|
||||
The manifest is authoritative for the selected runs. Failed runs remain failures: archive consistency is not a test pass. Every raw stream and receipt retains its original bytes. Identical input maps and ZIPs are stored by hash; the logical-file map preserves their original run paths. Historical capture helpers and exact source comparators are included as data, never executed by verification.
|
||||
|
||||
Source proofs compare every captured input with a named Git commit; the offline package verifier checks those receipts and their pinned comparator identity but does not repeat the Git comparison. The captured HEAD and the named proof commit retain their respective meanings. The verifier checks archive inventory, hashes, input ZIP contents, process/command/result consistency and executable artifact relationships. It does not establish hermetic builds, native workflow acceptance, UI integration, or release acceptance.
|
||||
|
||||
Use `--check-only` instead of `--output` for an in-memory consistency check. Existing output directories refuse. Python 3.11+ standard library is sufficient; `-B` avoids bytecode writes.
|
||||
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"prepare.py": {
|
||||
"bytes": 6312,
|
||||
"sha256": "242b5d26bee8dcac7f14a8e9556e92add336f03799c38039686a52aa23839759"
|
||||
},
|
||||
"verify.py": {
|
||||
"bytes": 11440,
|
||||
"sha256": "40d5a689202f254cc55144ada947d135a35fdf05bc182e3ec015aa64875343c0"
|
||||
},
|
||||
"README.md": {
|
||||
"bytes": 1629,
|
||||
"sha256": "3902ade7d0ee79918e6bfb68f5fab0e976e0248c3a9958b7b23eb1879ddead37"
|
||||
},
|
||||
"manifest.json": {
|
||||
"bytes": 14400,
|
||||
"sha256": "0f21337cdf3d29615b258bf1bed9d7449c359b20c2807ff0114d5d5d68946edd"
|
||||
},
|
||||
"evidence.zip": {
|
||||
"bytes": 6416449,
|
||||
"sha256": "211530e47aec6254ee1476081352139497091aae6fef249274c20aa1808b741f"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"kind": "scenario-group-planner-cargo-gates",
|
||||
"runs": {
|
||||
"planner-focused-1": {
|
||||
"command": [
|
||||
"cargo",
|
||||
"test",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"--message-format=json",
|
||||
"group_reorder",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"head": "d79a5ed37b2b64ac81d90137275fad8a97067418",
|
||||
"exitCode": 101,
|
||||
"inputCount": 804,
|
||||
"helper": "helpers/3e4cd38fd3807a4010a5a7102354f9139cab236ca55163c96090e416161e5fd2.py",
|
||||
"sourceProof": {
|
||||
"commit": "d79a5ed37b2b64ac81d90137275fad8a97067418",
|
||||
"pin": {
|
||||
"bytes": 596,
|
||||
"sha256": "93dcff027cb74e4fbd48c43c9b81b1d6d4fdadfaf4c35d12f302f4dfff1a04ca"
|
||||
},
|
||||
"comparator": "comparators/7805832d5f563ab2d7a50dd3de700792b5d6f459c68f7b1d8bcfcc9800fa0c3a.py"
|
||||
}
|
||||
},
|
||||
"planner-focused-2": {
|
||||
"command": [
|
||||
"cargo",
|
||||
"test",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"--message-format=json",
|
||||
"group_reorder",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"head": "7d3d7b23e768b66b93b608bcc6b4fb1e7f972f89",
|
||||
"exitCode": 0,
|
||||
"inputCount": 804,
|
||||
"helper": "helpers/3e4cd38fd3807a4010a5a7102354f9139cab236ca55163c96090e416161e5fd2.py",
|
||||
"sourceProof": {
|
||||
"commit": "7d3d7b23e768b66b93b608bcc6b4fb1e7f972f89",
|
||||
"pin": {
|
||||
"bytes": 596,
|
||||
"sha256": "ce40e7eeb5985eca7895f0c0e2c69f05d15d3d6133064d164e7aea4c88abfc78"
|
||||
},
|
||||
"comparator": "comparators/7805832d5f563ab2d7a50dd3de700792b5d6f459c68f7b1d8bcfcc9800fa0c3a.py"
|
||||
}
|
||||
},
|
||||
"scenario-regressions-1": {
|
||||
"command": [
|
||||
"cargo",
|
||||
"test",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"--message-format=json",
|
||||
"scenario",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"head": "7d3d7b23e768b66b93b608bcc6b4fb1e7f972f89",
|
||||
"exitCode": 0,
|
||||
"inputCount": 804,
|
||||
"helper": "helpers/3e4cd38fd3807a4010a5a7102354f9139cab236ca55163c96090e416161e5fd2.py",
|
||||
"sourceProof": {
|
||||
"commit": "7d3d7b23e768b66b93b608bcc6b4fb1e7f972f89",
|
||||
"pin": {
|
||||
"bytes": 601,
|
||||
"sha256": "61f82b2d7131ae97e4c36b7825d6dc60dbe93b1e8f8c909fa255838a8d065a78"
|
||||
},
|
||||
"comparator": "comparators/7805832d5f563ab2d7a50dd3de700792b5d6f459c68f7b1d8bcfcc9800fa0c3a.py"
|
||||
}
|
||||
},
|
||||
"format-1": {
|
||||
"command": [
|
||||
"cargo",
|
||||
"fmt",
|
||||
"--all",
|
||||
"--",
|
||||
"--check"
|
||||
],
|
||||
"head": "7d3d7b23e768b66b93b608bcc6b4fb1e7f972f89",
|
||||
"exitCode": 0,
|
||||
"inputCount": 804,
|
||||
"helper": "helpers/3e4cd38fd3807a4010a5a7102354f9139cab236ca55163c96090e416161e5fd2.py",
|
||||
"sourceProof": {
|
||||
"commit": "7d3d7b23e768b66b93b608bcc6b4fb1e7f972f89",
|
||||
"pin": {
|
||||
"bytes": 587,
|
||||
"sha256": "63ac957646cc9864555a1200635e8aa3c74f78e050ed04b97fd64cb75b4ea5b4"
|
||||
},
|
||||
"comparator": "comparators/7805832d5f563ab2d7a50dd3de700792b5d6f459c68f7b1d8bcfcc9800fa0c3a.py"
|
||||
}
|
||||
},
|
||||
"strict-lint-1": {
|
||||
"command": [
|
||||
"cargo",
|
||||
"clippy",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--all-targets",
|
||||
"--message-format=json",
|
||||
"--",
|
||||
"-D",
|
||||
"warnings"
|
||||
],
|
||||
"head": "7d3d7b23e768b66b93b608bcc6b4fb1e7f972f89",
|
||||
"exitCode": 0,
|
||||
"inputCount": 804,
|
||||
"helper": "helpers/3e4cd38fd3807a4010a5a7102354f9139cab236ca55163c96090e416161e5fd2.py",
|
||||
"sourceProof": {
|
||||
"commit": "7d3d7b23e768b66b93b608bcc6b4fb1e7f972f89",
|
||||
"pin": {
|
||||
"bytes": 592,
|
||||
"sha256": "9cd3e8c7065725fcf96bc738a279dc410c137b5a1b7b348e6351866314192431"
|
||||
},
|
||||
"comparator": "comparators/7805832d5f563ab2d7a50dd3de700792b5d6f459c68f7b1d8bcfcc9800fa0c3a.py"
|
||||
}
|
||||
}
|
||||
},
|
||||
"captureHelpers": {
|
||||
"gate.py": "helpers/3e4cd38fd3807a4010a5a7102354f9139cab236ca55163c96090e416161e5fd2.py",
|
||||
"source-proof.py": "helpers/c86f51d9b8465341dab73d045923ec306b15ebabf7c97b81beac7afe73bd72a1.py"
|
||||
},
|
||||
"logicalFiles": {
|
||||
"cargo/format-1/inputs.json": "capsules/e307e14fc0ec93bc6ffedd8f7155cdb398651e31cb11cb68ff5ae8160fd507d6.json",
|
||||
"cargo/format-1/inputs.zip": "capsules/2d208776732619a1a7bdd2c7275b74db5efc9e3bf5606723afc8cb3c225027fe.zip",
|
||||
"cargo/format-1/process.json": "cargo/format-1/process.json",
|
||||
"cargo/format-1/result.json": "cargo/format-1/result.json",
|
||||
"cargo/format-1/source-proof.json": "cargo/format-1/source-proof.json",
|
||||
"cargo/format-1/started.json": "cargo/format-1/started.json",
|
||||
"cargo/format-1/stderr.log": "cargo/format-1/stderr.log",
|
||||
"cargo/format-1/stdout.log": "cargo/format-1/stdout.log",
|
||||
"cargo/planner-focused-1/inputs.json": "capsules/886dfd71d5c504069e8616115fbf32bc8191d9284bb11a334789b6b26414ff2a.json",
|
||||
"cargo/planner-focused-1/inputs.zip": "capsules/78421097bc1e5c3c09feaac55c2a4cab158ba4477b43a443e38605b177ca06c2.zip",
|
||||
"cargo/planner-focused-1/process.json": "cargo/planner-focused-1/process.json",
|
||||
"cargo/planner-focused-1/result.json": "cargo/planner-focused-1/result.json",
|
||||
"cargo/planner-focused-1/source-proof.json": "cargo/planner-focused-1/source-proof.json",
|
||||
"cargo/planner-focused-1/started.json": "cargo/planner-focused-1/started.json",
|
||||
"cargo/planner-focused-1/stderr.log": "cargo/planner-focused-1/stderr.log",
|
||||
"cargo/planner-focused-1/stdout.log": "cargo/planner-focused-1/stdout.log",
|
||||
"cargo/planner-focused-2/inputs.json": "capsules/e307e14fc0ec93bc6ffedd8f7155cdb398651e31cb11cb68ff5ae8160fd507d6.json",
|
||||
"cargo/planner-focused-2/inputs.zip": "capsules/2d208776732619a1a7bdd2c7275b74db5efc9e3bf5606723afc8cb3c225027fe.zip",
|
||||
"cargo/planner-focused-2/process.json": "cargo/planner-focused-2/process.json",
|
||||
"cargo/planner-focused-2/result.json": "cargo/planner-focused-2/result.json",
|
||||
"cargo/planner-focused-2/source-proof.json": "cargo/planner-focused-2/source-proof.json",
|
||||
"cargo/planner-focused-2/started.json": "cargo/planner-focused-2/started.json",
|
||||
"cargo/planner-focused-2/stderr.log": "cargo/planner-focused-2/stderr.log",
|
||||
"cargo/planner-focused-2/stdout.log": "cargo/planner-focused-2/stdout.log",
|
||||
"cargo/scenario-regressions-1/inputs.json": "capsules/e307e14fc0ec93bc6ffedd8f7155cdb398651e31cb11cb68ff5ae8160fd507d6.json",
|
||||
"cargo/scenario-regressions-1/inputs.zip": "capsules/2d208776732619a1a7bdd2c7275b74db5efc9e3bf5606723afc8cb3c225027fe.zip",
|
||||
"cargo/scenario-regressions-1/process.json": "cargo/scenario-regressions-1/process.json",
|
||||
"cargo/scenario-regressions-1/result.json": "cargo/scenario-regressions-1/result.json",
|
||||
"cargo/scenario-regressions-1/source-proof.json": "cargo/scenario-regressions-1/source-proof.json",
|
||||
"cargo/scenario-regressions-1/started.json": "cargo/scenario-regressions-1/started.json",
|
||||
"cargo/scenario-regressions-1/stderr.log": "cargo/scenario-regressions-1/stderr.log",
|
||||
"cargo/scenario-regressions-1/stdout.log": "cargo/scenario-regressions-1/stdout.log",
|
||||
"cargo/strict-lint-1/inputs.json": "capsules/e307e14fc0ec93bc6ffedd8f7155cdb398651e31cb11cb68ff5ae8160fd507d6.json",
|
||||
"cargo/strict-lint-1/inputs.zip": "capsules/2d208776732619a1a7bdd2c7275b74db5efc9e3bf5606723afc8cb3c225027fe.zip",
|
||||
"cargo/strict-lint-1/process.json": "cargo/strict-lint-1/process.json",
|
||||
"cargo/strict-lint-1/result.json": "cargo/strict-lint-1/result.json",
|
||||
"cargo/strict-lint-1/source-proof.json": "cargo/strict-lint-1/source-proof.json",
|
||||
"cargo/strict-lint-1/started.json": "cargo/strict-lint-1/started.json",
|
||||
"cargo/strict-lint-1/stderr.log": "cargo/strict-lint-1/stderr.log",
|
||||
"cargo/strict-lint-1/stdout.log": "cargo/strict-lint-1/stdout.log",
|
||||
"comparators/7805832d5f563ab2d7a50dd3de700792b5d6f459c68f7b1d8bcfcc9800fa0c3a.py": "comparators/7805832d5f563ab2d7a50dd3de700792b5d6f459c68f7b1d8bcfcc9800fa0c3a.py",
|
||||
"helpers/3e4cd38fd3807a4010a5a7102354f9139cab236ca55163c96090e416161e5fd2.py": "helpers/3e4cd38fd3807a4010a5a7102354f9139cab236ca55163c96090e416161e5fd2.py",
|
||||
"helpers/c86f51d9b8465341dab73d045923ec306b15ebabf7c97b81beac7afe73bd72a1.py": "helpers/c86f51d9b8465341dab73d045923ec306b15ebabf7c97b81beac7afe73bd72a1.py"
|
||||
},
|
||||
"members": {
|
||||
"capsules/2d208776732619a1a7bdd2c7275b74db5efc9e3bf5606723afc8cb3c225027fe.zip": {
|
||||
"bytes": 3218365,
|
||||
"sha256": "2d208776732619a1a7bdd2c7275b74db5efc9e3bf5606723afc8cb3c225027fe"
|
||||
},
|
||||
"capsules/78421097bc1e5c3c09feaac55c2a4cab158ba4477b43a443e38605b177ca06c2.zip": {
|
||||
"bytes": 3218350,
|
||||
"sha256": "78421097bc1e5c3c09feaac55c2a4cab158ba4477b43a443e38605b177ca06c2"
|
||||
},
|
||||
"capsules/886dfd71d5c504069e8616115fbf32bc8191d9284bb11a334789b6b26414ff2a.json": {
|
||||
"bytes": 134988,
|
||||
"sha256": "886dfd71d5c504069e8616115fbf32bc8191d9284bb11a334789b6b26414ff2a"
|
||||
},
|
||||
"capsules/e307e14fc0ec93bc6ffedd8f7155cdb398651e31cb11cb68ff5ae8160fd507d6.json": {
|
||||
"bytes": 134988,
|
||||
"sha256": "e307e14fc0ec93bc6ffedd8f7155cdb398651e31cb11cb68ff5ae8160fd507d6"
|
||||
},
|
||||
"cargo/format-1/process.json": {
|
||||
"bytes": 116,
|
||||
"sha256": "70ca560612155befea0c80a2cb5e21db594491bd6aab041cfb67838b589cb09b"
|
||||
},
|
||||
"cargo/format-1/result.json": {
|
||||
"bytes": 1869,
|
||||
"sha256": "ed99994b3949f4c7c098daec09f7c2c6a26604553c575ac1bc37fe7be8c1f835"
|
||||
},
|
||||
"cargo/format-1/source-proof.json": {
|
||||
"bytes": 587,
|
||||
"sha256": "63ac957646cc9864555a1200635e8aa3c74f78e050ed04b97fd64cb75b4ea5b4"
|
||||
},
|
||||
"cargo/format-1/started.json": {
|
||||
"bytes": 1480,
|
||||
"sha256": "ecb67c04dccedcd46066d001be2ded356610d4f4826c20f6ca9a778f3f7be050"
|
||||
},
|
||||
"cargo/format-1/stderr.log": {
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"cargo/format-1/stdout.log": {
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"cargo/planner-focused-1/process.json": {
|
||||
"bytes": 206,
|
||||
"sha256": "cfc8eb78e28367ef82a2f437f3b749c96599707211333ce25a9d253e2438e0ad"
|
||||
},
|
||||
"cargo/planner-focused-1/result.json": {
|
||||
"bytes": 1993,
|
||||
"sha256": "550148e118132c776621579dc31c0fea628201f392da446854dd6ccdeef2268e"
|
||||
},
|
||||
"cargo/planner-focused-1/source-proof.json": {
|
||||
"bytes": 596,
|
||||
"sha256": "93dcff027cb74e4fbd48c43c9b81b1d6d4fdadfaf4c35d12f302f4dfff1a04ca"
|
||||
},
|
||||
"cargo/planner-focused-1/started.json": {
|
||||
"bytes": 1595,
|
||||
"sha256": "1780b84f863f5947b8514f85f07faa1249e99c01dc8999fc9352cfc562c3cfa4"
|
||||
},
|
||||
"cargo/planner-focused-1/stderr.log": {
|
||||
"bytes": 411,
|
||||
"sha256": "ac34a07cf32fc7ea9db97e71e03ec4dbe96ba4d45456aac54e4910432ad6873f"
|
||||
},
|
||||
"cargo/planner-focused-1/stdout.log": {
|
||||
"bytes": 307738,
|
||||
"sha256": "32c704b9cf7c722e8e84a0f4842f953737a983a44c67374515a5ce968df71e53"
|
||||
},
|
||||
"cargo/planner-focused-2/process.json": {
|
||||
"bytes": 206,
|
||||
"sha256": "b97bb844bb9e1aa92b3a6d387a86547a36f7fe7b20483b13f9dbdf5bca1f87fa"
|
||||
},
|
||||
"cargo/planner-focused-2/result.json": {
|
||||
"bytes": 3320,
|
||||
"sha256": "a622c0e9bf572b8d23ad41c0024d5cc3e05fcb0323c464c7c50c0f694726b57e"
|
||||
},
|
||||
"cargo/planner-focused-2/source-proof.json": {
|
||||
"bytes": 596,
|
||||
"sha256": "ce40e7eeb5985eca7895f0c0e2c69f05d15d3d6133064d164e7aea4c88abfc78"
|
||||
},
|
||||
"cargo/planner-focused-2/started.json": {
|
||||
"bytes": 1595,
|
||||
"sha256": "39c0b687cb212389b4fe2f75900c7a21dc0de7f15da4583f1889d91e697fbde2"
|
||||
},
|
||||
"cargo/planner-focused-2/stderr.log": {
|
||||
"bytes": 284,
|
||||
"sha256": "8c86923ad2d9f97c60fd6c93c1d565c656f47e979819e3ded0d9cc2c11b6b810"
|
||||
},
|
||||
"cargo/planner-focused-2/stdout.log": {
|
||||
"bytes": 295615,
|
||||
"sha256": "4bcceeace911767afd99b5f58e628825453ff699782bab147de9e62c100adcc6"
|
||||
},
|
||||
"cargo/scenario-regressions-1/process.json": {
|
||||
"bytes": 201,
|
||||
"sha256": "f4fed7b6cff71de94a9eee3677bb257f79eee183a8e72a811f1d1225d0450b53"
|
||||
},
|
||||
"cargo/scenario-regressions-1/result.json": {
|
||||
"bytes": 3314,
|
||||
"sha256": "bff168dbb514672e80c1a0e3a74c4ae222ef97fc1b2dc9e0f3d5763d900fb93c"
|
||||
},
|
||||
"cargo/scenario-regressions-1/source-proof.json": {
|
||||
"bytes": 601,
|
||||
"sha256": "61f82b2d7131ae97e4c36b7825d6dc60dbe93b1e8f8c909fa255838a8d065a78"
|
||||
},
|
||||
"cargo/scenario-regressions-1/started.json": {
|
||||
"bytes": 1590,
|
||||
"sha256": "1360f7e14a269a64d040cdbddac11e23c4bd2accd59ab0805a91ff5154c88278"
|
||||
},
|
||||
"cargo/scenario-regressions-1/stderr.log": {
|
||||
"bytes": 210,
|
||||
"sha256": "504bdaa5c008c52a9462aeca71413f9933a4e9c6b0272715ba5ca916411781c1"
|
||||
},
|
||||
"cargo/scenario-regressions-1/stdout.log": {
|
||||
"bytes": 324556,
|
||||
"sha256": "423497185a1e5fdd26cac3af041dd9b95cce6a1ab7b4f8edcdbfe0d8ab28ac5f"
|
||||
},
|
||||
"cargo/strict-lint-1/process.json": {
|
||||
"bytes": 179,
|
||||
"sha256": "2db232b16369d1ac79a81f6e0ca6f7f42c11afa9c333564e2674452463f2a63b"
|
||||
},
|
||||
"cargo/strict-lint-1/result.json": {
|
||||
"bytes": 1959,
|
||||
"sha256": "116208e1d3cd913068d6b7d894a99e4afe56444c7adf14fb4271c279c7d1d7e8"
|
||||
},
|
||||
"cargo/strict-lint-1/source-proof.json": {
|
||||
"bytes": 592,
|
||||
"sha256": "9cd3e8c7065725fcf96bc738a279dc410c137b5a1b7b348e6351866314192431"
|
||||
},
|
||||
"cargo/strict-lint-1/started.json": {
|
||||
"bytes": 1563,
|
||||
"sha256": "6018965fbef137071589d0f38f1005cbe11f1c3b8d3d4feb71bfdf554d3868a0"
|
||||
},
|
||||
"cargo/strict-lint-1/stderr.log": {
|
||||
"bytes": 381,
|
||||
"sha256": "3030ae32e30806c0c92cac0d876c8284e4bbcb81899bdd73f6587df4f5a316d2"
|
||||
},
|
||||
"cargo/strict-lint-1/stdout.log": {
|
||||
"bytes": 269439,
|
||||
"sha256": "50ae306ee4baacc73c51810ba4c41e2162ce7ef137ad104165d2f30f7fe90a60"
|
||||
},
|
||||
"comparators/7805832d5f563ab2d7a50dd3de700792b5d6f459c68f7b1d8bcfcc9800fa0c3a.py": {
|
||||
"bytes": 10094,
|
||||
"sha256": "7805832d5f563ab2d7a50dd3de700792b5d6f459c68f7b1d8bcfcc9800fa0c3a"
|
||||
},
|
||||
"helpers/3e4cd38fd3807a4010a5a7102354f9139cab236ca55163c96090e416161e5fd2.py": {
|
||||
"bytes": 3778,
|
||||
"sha256": "3e4cd38fd3807a4010a5a7102354f9139cab236ca55163c96090e416161e5fd2"
|
||||
},
|
||||
"helpers/c86f51d9b8465341dab73d045923ec306b15ebabf7c97b81beac7afe73bd72a1.py": {
|
||||
"bytes": 1396,
|
||||
"sha256": "c86f51d9b8465341dab73d045923ec306b15ebabf7c97b81beac7afe73bd72a1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
"""Package explicitly selected closed gate captures without running Cargo."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import json
|
||||
import zipfile
|
||||
from verify import RAW_FILES, pin, parsed, need, run_name, verify_data, verify_package
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def collect(root, runs, helpers, comparators=()):
|
||||
gates = root / ".local/scenario-group-gates"
|
||||
candidates = [gates / name for name in ("gate.py", "source-proof.py")] + helpers
|
||||
helper_bytes = [path.read_bytes() for path in candidates]
|
||||
comparator_paths = [root / ".local/process-source-evidence-tools/verify.py", *comparators]
|
||||
comparator_bytes = [path.read_bytes() for path in comparator_paths if path.is_file()]
|
||||
payload, logical, specs = {}, {}, {}
|
||||
def add(name, data):
|
||||
stored = ("capsules/" + pin(data)["sha256"] + Path(name).suffix
|
||||
if name.rsplit("/", 1)[1] in {"inputs.json", "inputs.zip"} else name)
|
||||
need(stored not in payload or payload[stored] == data, "deduplication collision")
|
||||
payload[stored] = data
|
||||
logical[name] = stored
|
||||
helper_catalog = {}
|
||||
for name, data in zip(("gate.py", "source-proof.py"), helper_bytes):
|
||||
helper = "helpers/" + pin(data)["sha256"] + ".py"
|
||||
add(helper, data)
|
||||
helper_catalog[name] = helper
|
||||
for run in runs:
|
||||
folder = gates / run
|
||||
need(folder.is_dir(), "run directory missing: " + run)
|
||||
paths = list(folder.iterdir())
|
||||
names = {path.name for path in paths}
|
||||
need(all(path.is_file() and not path.is_symlink() for path in paths)
|
||||
and names in (RAW_FILES, RAW_FILES | {"source-proof.json"}), "exact closed raw files required: " + run)
|
||||
raw = {path.name: path.read_bytes() for path in paths}
|
||||
result = parsed(raw["result.json"])
|
||||
need("finish" in result and result["inputsUnchanged"] is True, "unfinished/changed run refused: " + run)
|
||||
matches = [data for data in helper_bytes if pin(data) == result["helper"]]
|
||||
need(bool(matches), "exact historical helper unavailable for " + run + "; supply --helper PATH")
|
||||
helper = "helpers/" + result["helper"]["sha256"] + ".py"
|
||||
add(helper, matches[0])
|
||||
for name, data in sorted(raw.items()):
|
||||
add("cargo/" + run + "/" + name, data)
|
||||
specs[run] = {key: result[key] for key in ("command", "head", "exitCode", "inputCount")}
|
||||
specs[run]["helper"] = helper
|
||||
if "source-proof.json" in raw:
|
||||
proof = parsed(raw["source-proof.json"])
|
||||
matches = [data for data in comparator_bytes if pin(data) == proof["comparator"]]
|
||||
need(bool(matches), "exact source comparator unavailable for " + run + "; supply --comparator PATH")
|
||||
comparator = "comparators/" + proof["comparator"]["sha256"] + ".py"
|
||||
add(comparator, matches[0])
|
||||
specs[run]["sourceProof"] = {"commit": proof["commit"], "pin": pin(raw["source-proof.json"]), "comparator": comparator}
|
||||
manifest = {"schema": 1, "kind": "scenario-group-planner-cargo-gates", "runs": specs,
|
||||
"captureHelpers": helper_catalog,
|
||||
"logicalFiles": dict(sorted(logical.items())),
|
||||
"members": {name: pin(data) for name, data in sorted(payload.items())}}
|
||||
summary = verify_data(manifest, payload.__getitem__)
|
||||
return payload, manifest, summary
|
||||
|
||||
|
||||
def write_json(path, value):
|
||||
with path.open("xb") as output:
|
||||
output.write((json.dumps(value, indent=2) + "\n").encode("utf-8"))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--root", type=Path, required=True, help="checkout holding .local/scenario-group-gates")
|
||||
parser.add_argument("--runs", nargs="+", required=True, help="explicit run names, including failures to retain")
|
||||
parser.add_argument("--helper", type=Path, action="append", default=[], help="additional exact historical helper (repeatable)")
|
||||
parser.add_argument("--comparator", type=Path, action="append", default=[], help="additional exact historical source comparator (repeatable)")
|
||||
parser.add_argument("--output", type=Path, help="fresh, nonexistent package directory (required unless --check-only)")
|
||||
parser.add_argument("--check-only", action="store_true", help="validate raw captures in memory without creating an archive")
|
||||
args = parser.parse_args()
|
||||
need(all(run_name(run) for run in args.runs) and len(args.runs) == len(set(args.runs)), "unique safe run names required")
|
||||
need(args.check_only or args.output is not None, "--output is required unless --check-only")
|
||||
if not args.check_only:
|
||||
need(not args.output.exists(), "output must be a fresh directory: " + str(args.output))
|
||||
payload, manifest, summary = collect(args.root.resolve(), args.runs, args.helper, args.comparator)
|
||||
if args.check_only:
|
||||
print("PASS raw consistency; no package written:", summary)
|
||||
return
|
||||
out = args.output.resolve()
|
||||
out.mkdir(parents=True, exist_ok=False)
|
||||
for name in ("evidence.zip", "manifest.json", "files.json"):
|
||||
need(not (out / name).exists(), "existing package output refused: " + name)
|
||||
for name in ("prepare.py", "verify.py", "README.md"):
|
||||
data = (HERE / name).read_bytes()
|
||||
destination = out / name
|
||||
if destination.exists():
|
||||
need(destination.read_bytes() == data, "different existing package tool: " + name)
|
||||
else:
|
||||
with destination.open("xb") as output:
|
||||
output.write(data)
|
||||
with zipfile.ZipFile(out / "evidence.zip", "x", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive:
|
||||
for name, data in sorted(payload.items()):
|
||||
info = zipfile.ZipInfo(name, date_time=(2026, 9, 23, 0, 0, 0))
|
||||
info.compress_type = zipfile.ZIP_DEFLATED
|
||||
info.external_attr = 0o100644 << 16
|
||||
archive.writestr(info, data)
|
||||
write_json(out / "manifest.json", manifest)
|
||||
names = ("prepare.py", "verify.py", "README.md", "manifest.json", "evidence.zip")
|
||||
write_json(out / "files.json", {name: pin((out / name).read_bytes()) for name in names})
|
||||
need(verify_package(out) == summary, "written package differs from validated captures")
|
||||
print("PASS package readback:", out, summary)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Exercise packaging of explicit real closed runs; mutate only in-memory test copies."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import contextlib
|
||||
import copy
|
||||
import io
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
import prepare
|
||||
from verify import RAW_FILES, need, pin, run_name, verify_data, verify_package
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
parser.add_argument("--runs", nargs="+", required=True)
|
||||
parser.add_argument("--output", type=Path, required=True, help="fresh package under this tools directory")
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
output = args.output.resolve()
|
||||
need(output.is_relative_to(prepare.HERE) and output != prepare.HERE,
|
||||
"selfcheck output must stay beneath the tools directory")
|
||||
need(all(run_name(run) for run in args.runs) and len(args.runs) == len(set(args.runs)),
|
||||
"unique explicit run names")
|
||||
need(not output.exists(), "fresh selfcheck output required")
|
||||
for path in prepare.HERE.glob("*.py"):
|
||||
compile(path.read_bytes(), str(path), "exec")
|
||||
payload, manifest, expected = prepare.collect(root, args.runs, [])
|
||||
command = [sys.executable, "-B", str(prepare.HERE / "prepare.py"),
|
||||
"--root", str(root), "--runs", *args.runs, "--output", str(output)]
|
||||
subprocess.run(command, check=True)
|
||||
need(verify_package(output) == expected, "actual ZIP readback summary")
|
||||
gates = root / ".local/scenario-group-gates"
|
||||
with zipfile.ZipFile(output / "evidence.zip") as archive:
|
||||
for run in args.runs:
|
||||
for name in RAW_FILES | ({"source-proof.json"} if "sourceProof" in manifest["runs"][run] else set()):
|
||||
logical = "cargo/" + run + "/" + name
|
||||
need(archive.read(manifest["logicalFiles"][logical]) == (gates / run / name).read_bytes(),
|
||||
"original raw byte equality: " + logical)
|
||||
for name, stored in manifest["captureHelpers"].items():
|
||||
need(archive.read(stored) == (gates / name).read_bytes(), "named original helper: " + name)
|
||||
repeats = subprocess.run(command, capture_output=True, text=True)
|
||||
need(repeats.returncode != 0 and "fresh directory" in repeats.stderr,
|
||||
"existing output must be refused")
|
||||
|
||||
checked = []
|
||||
def rejected(label, changed, altered):
|
||||
try:
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
verify_data(changed, altered.__getitem__)
|
||||
except (ValueError, KeyError, zipfile.BadZipFile):
|
||||
checked.append(label)
|
||||
else:
|
||||
raise AssertionError("corruption accepted: " + label)
|
||||
|
||||
run = args.runs[0]
|
||||
prefix = "cargo/" + run + "/"
|
||||
# Re-pin the outer member to ensure the verifier reaches receipt relationships.
|
||||
altered = dict(payload)
|
||||
changed = copy.deepcopy(manifest)
|
||||
stream = changed["logicalFiles"][prefix + "stdout.log"]
|
||||
altered[stream] += b"\nselfcheck changed log\n"
|
||||
changed["members"][stream] = pin(altered[stream])
|
||||
rejected("raw stream/receipt mismatch", changed, altered)
|
||||
|
||||
altered = dict(payload)
|
||||
changed = copy.deepcopy(manifest)
|
||||
helper = changed["runs"][run]["helper"]
|
||||
altered[helper] += b"\n# selfcheck changed helper\n"
|
||||
changed["members"][helper] = pin(altered[helper])
|
||||
rejected("helper bytes/name mismatch", changed, altered)
|
||||
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["runs"][run]["exitCode"] = 99
|
||||
rejected("run/result exit mismatch", changed, payload)
|
||||
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["logicalFiles"][prefix + "inputs.zip"] = changed["logicalFiles"][prefix + "inputs.json"]
|
||||
rejected("input archive mapping mismatch", changed, payload)
|
||||
|
||||
changed = copy.deepcopy(manifest)
|
||||
del changed["logicalFiles"][prefix + "result.json"]
|
||||
rejected("missing closed receipt", changed, payload)
|
||||
for proof_run, spec in manifest["runs"].items():
|
||||
if "sourceProof" not in spec:
|
||||
continue
|
||||
proof_path = "cargo/" + proof_run + "/source-proof.json"
|
||||
for field, value in [("run", "wrong-run"), ("commit", "0"*40), ("result", {"bytes":0,"sha256":"0"*64}), ("inputCount", 1)]:
|
||||
altered = dict(payload)
|
||||
changed = copy.deepcopy(manifest)
|
||||
proof = json.loads(altered[proof_path])
|
||||
proof[field] = value
|
||||
altered[proof_path] = json.dumps(proof).encode()
|
||||
changed["members"][proof_path] = pin(altered[proof_path])
|
||||
changed["runs"][proof_run]["sourceProof"]["pin"] = pin(altered[proof_path])
|
||||
rejected("source proof " + field + " relationship", changed, altered)
|
||||
changed = copy.deepcopy(manifest)
|
||||
del changed["logicalFiles"][proof_path]
|
||||
rejected("missing declared optional source proof", changed, payload)
|
||||
altered = dict(payload)
|
||||
changed = copy.deepcopy(manifest)
|
||||
comparator = spec["sourceProof"]["comparator"]
|
||||
altered[comparator] += b"\n# corrupted comparator\n"
|
||||
changed["members"][comparator] = pin(altered[comparator])
|
||||
rejected("source comparator bytes mismatch", changed, altered)
|
||||
break
|
||||
print("PASS original raw/optional-proof/helper archive equality, existing-output refusal, and corruption rejection:", checked)
|
||||
print("Historical captured failures remain failures:", expected["recordedFailures"])
|
||||
print("No Cargo, archived code, or GUI executed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,194 @@
|
||||
"""Offline consistency checks only; never execute or extract archived inputs."""
|
||||
from pathlib import Path, PurePosixPath
|
||||
from datetime import datetime
|
||||
import argparse
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import stat
|
||||
import zipfile
|
||||
|
||||
RAW_FILES = {"started.json", "process.json", "result.json", "stdout.log", "stderr.log", "inputs.json", "inputs.zip"}
|
||||
OUTER_FILES = {"prepare.py", "verify.py", "README.md", "manifest.json", "evidence.zip"}
|
||||
|
||||
|
||||
def need(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def pin(data):
|
||||
return {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
|
||||
|
||||
|
||||
def valid_pin(value):
|
||||
return (isinstance(value, dict) and set(value) == {"bytes", "sha256"}
|
||||
and type(value["bytes"]) is int and value["bytes"] >= 0
|
||||
and isinstance(value["sha256"], str)
|
||||
and re.fullmatch(r"[0-9a-f]{64}", value["sha256"]) is not None)
|
||||
|
||||
|
||||
def safe(name):
|
||||
return (isinstance(name, str) and bool(name) and "\\" not in name and ":" not in name
|
||||
and all(ord(char) >= 32 for char in name)
|
||||
and not PurePosixPath(name).is_absolute()
|
||||
and all(part not in ("", ".", "..") for part in name.split("/")))
|
||||
|
||||
|
||||
def run_name(name):
|
||||
return isinstance(name, str) and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", name) is not None
|
||||
|
||||
|
||||
def parsed(data):
|
||||
def pairs(items):
|
||||
result = {}
|
||||
for key, value in items:
|
||||
need(key not in result, "duplicate JSON key: " + key)
|
||||
result[key] = value
|
||||
return result
|
||||
def constant(value):
|
||||
raise ValueError("nonfinite JSON constant: " + value)
|
||||
return json.loads(data, object_pairs_hook=pairs, parse_constant=constant)
|
||||
|
||||
|
||||
def inventory(archive, expected):
|
||||
names = archive.namelist()
|
||||
need(len(names) == len(set(names)) and set(names) == set(expected), "exact ZIP membership")
|
||||
for entry in archive.infolist():
|
||||
need(safe(entry.filename) and not entry.is_dir(), "unsafe ZIP member: " + entry.filename)
|
||||
need(not stat.S_ISLNK(entry.external_attr >> 16), "ZIP symlink: " + entry.filename)
|
||||
need(valid_pin(expected[entry.filename]), "invalid member pin: " + entry.filename)
|
||||
need(entry.file_size == expected[entry.filename]["bytes"], "ZIP recorded size: " + entry.filename)
|
||||
|
||||
|
||||
def ordered_times(*values):
|
||||
times = [datetime.fromisoformat(value) for value in values]
|
||||
return all(value.utcoffset() is not None for value in times) and times == sorted(times)
|
||||
|
||||
|
||||
def verify_data(manifest, read):
|
||||
"""Shared read-only verifier for packaged bytes or prepare's in-memory payload."""
|
||||
need(manifest["schema"] == 1 and manifest["kind"] == "scenario-group-planner-cargo-gates", "manifest schema")
|
||||
runs, logical, stored = manifest["runs"], manifest["logicalFiles"], manifest["members"]
|
||||
helpers = manifest["captureHelpers"]
|
||||
need(isinstance(helpers, dict) and set(helpers) == {"gate.py", "source-proof.py"}, "capture helper names")
|
||||
need(all(isinstance(name, str) and re.fullmatch(r"helpers/[0-9a-f]{64}\.py", name)
|
||||
for name in helpers.values()), "capture helper mapping")
|
||||
need(isinstance(runs, dict) and bool(runs) and all(run_name(run) for run in runs), "explicit run inventory")
|
||||
need(all(safe(name) for name in logical) and all(safe(name) for name in stored), "unsafe manifest path")
|
||||
need(set(logical.values()) == set(stored), "unreferenced or absent stored members")
|
||||
for name, expected in stored.items():
|
||||
need(valid_pin(expected) and pin(read(name)) == expected, "stored member hash: " + name)
|
||||
expected_logical = {"cargo/" + run + "/" + name for run in runs for name in RAW_FILES}
|
||||
expected_logical.update(spec["helper"] for spec in runs.values())
|
||||
expected_logical.update(helpers.values())
|
||||
for run, spec in runs.items():
|
||||
if "sourceProof" in spec:
|
||||
proof_spec = spec["sourceProof"]
|
||||
need(set(proof_spec) == {"commit", "pin", "comparator"}, run + ": exact source proof spec")
|
||||
need(isinstance(proof_spec["commit"], str) and re.fullmatch(r"[0-9a-f]{40}", proof_spec["commit"]), run + ": named full source commit")
|
||||
need(valid_pin(proof_spec["pin"]), run + ": source proof pin")
|
||||
need(isinstance(proof_spec["comparator"], str) and re.fullmatch(r"comparators/[0-9a-f]{64}\.py", proof_spec["comparator"]), run + ": comparator path")
|
||||
expected_logical.update({"cargo/" + run + "/source-proof.json", proof_spec["comparator"]})
|
||||
need(set(logical) == expected_logical, "exact logical raw/helper inventory")
|
||||
for name, destination in logical.items():
|
||||
if name.startswith("cargo/") and name.rsplit("/", 1)[1] in {"inputs.json", "inputs.zip"}:
|
||||
expected = "capsules/" + stored[destination]["sha256"] + PurePosixPath(name).suffix
|
||||
need(destination == expected, "input capsule mapping: " + name)
|
||||
else:
|
||||
need(destination == name, "only input maps/archives may be deduplicated: " + name)
|
||||
def raw(name):
|
||||
return read(logical[name])
|
||||
for name, helper in helpers.items():
|
||||
need(helper == "helpers/" + pin(raw(helper))["sha256"] + ".py", "named helper hash: " + name)
|
||||
seen_capsules = set()
|
||||
for run, spec in runs.items():
|
||||
prefix = "cargo/" + run + "/"
|
||||
start = parsed(raw(prefix + "started.json"))
|
||||
process = parsed(raw(prefix + "process.json"))
|
||||
result = parsed(raw(prefix + "result.json"))
|
||||
required_start = {"start", "command", "cwd", "head", "scope", "inputs", "archive",
|
||||
"inputCount", "environment", "helper", "cargo", "rustc"}
|
||||
need(required_start <= start.keys(), run + ": incomplete start receipt")
|
||||
need(all(result.get(key) == value for key, value in start.items()), run + ": start/result relationship")
|
||||
command = result["command"]
|
||||
need(isinstance(command, list) and command and command[0] == "cargo"
|
||||
and all(isinstance(arg, str) for arg in command), run + ": Cargo command")
|
||||
need(command == process["command"] == spec["command"], run + ": command relationship")
|
||||
need(result["head"] == spec["head"] and re.fullmatch(r"(?:[0-9a-f]{40}|[0-9a-f]{64})", result["head"]), run + ": HEAD")
|
||||
need(type(result["exitCode"]) is int and result["exitCode"] == spec["exitCode"], run + ": recorded exit")
|
||||
need(result["inputsUnchanged"] is True, run + ": changed inputs refused")
|
||||
need(type(result["seconds"]) in (int, float) and math.isfinite(result["seconds"])
|
||||
and result["seconds"] >= 0, run + ": duration")
|
||||
need(type(process["pid"]) is int and process["pid"] > 0, run + ": process identity")
|
||||
need(ordered_times(start["start"], process["observed"], result["finish"]), run + ": completed timestamp order")
|
||||
for field, filename in [("inputs", "inputs.json"), ("archive", "inputs.zip"), ("stdout", "stdout.log"), ("stderr", "stderr.log")]:
|
||||
need(result[field] == pin(raw(prefix + filename)), run + ": " + field + " pin")
|
||||
need(valid_pin(result["helper"]) and result["helper"] == pin(raw(spec["helper"])), run + ": helper pin")
|
||||
need(spec["helper"] == "helpers/" + result["helper"]["sha256"] + ".py", run + ": helper mapping")
|
||||
inputs = parsed(raw(prefix + "inputs.json"))
|
||||
need(isinstance(inputs, dict) and all(safe(name) for name in inputs), run + ": safe input inventory")
|
||||
need(type(result["inputCount"]) is int and len(inputs) == result["inputCount"] == spec["inputCount"], run + ": input counts")
|
||||
if "sourceProof" in spec:
|
||||
proof_spec = spec["sourceProof"]
|
||||
proof_bytes = raw(prefix + "source-proof.json")
|
||||
proof = parsed(proof_bytes)
|
||||
need(pin(proof_bytes) == proof_spec["pin"], run + ": source proof pinned bytes")
|
||||
need(proof["run"] == run and proof["commit"] == proof_spec["commit"], run + ": proof run/commit identity")
|
||||
need(proof["status"] == "PASS exact captured input inventory and Git blob bytes", run + ": source proof status")
|
||||
need(proof["inputCount"] == len(inputs) and proof["inputMap"] == result["inputs"], run + ": proof input relationship")
|
||||
need(proof["result"] == pin(raw(prefix + "result.json")), run + ": proof result relationship")
|
||||
need(ordered_times(result["finish"], proof["verified"]), run + ": proof time after closed gate")
|
||||
comparator = proof_spec["comparator"]
|
||||
need(valid_pin(proof["comparator"]) and pin(raw(comparator)) == proof["comparator"], run + ": comparator bytes")
|
||||
need(comparator == "comparators/" + proof["comparator"]["sha256"] + ".py", run + ": comparator identity")
|
||||
need(sum(name.startswith("crates/vendor/iced_widget/") for name in inputs) == 71, run + ": vendor count")
|
||||
capsule = (logical[prefix + "inputs.json"], logical[prefix + "inputs.zip"])
|
||||
if capsule not in seen_capsules:
|
||||
with zipfile.ZipFile(io.BytesIO(raw(prefix + "inputs.zip"))) as source:
|
||||
inventory(source, inputs)
|
||||
for name, expected in inputs.items():
|
||||
need(pin(source.read(name)) == expected, run + ": source hash " + name)
|
||||
seen_capsules.add(capsule)
|
||||
artifacts = []
|
||||
for line in raw(prefix + "stdout.log").decode("utf-8", errors="replace").splitlines():
|
||||
try:
|
||||
value = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if isinstance(value, dict) and value.get("reason") == "compiler-artifact" and value.get("executable"):
|
||||
artifacts.append(value)
|
||||
need(isinstance(result["artifacts"], list), run + ": artifact records")
|
||||
need(artifacts == [record["cargo"] for record in result["artifacts"]], run + ": Cargo JSON artifact relationship")
|
||||
for record in result["artifacts"]:
|
||||
need(set(record) == {"cargo", "readback"}
|
||||
and (record["readback"] is None or valid_pin(record["readback"])), run + ": artifact readback pin")
|
||||
print(run, "recorded exit", result["exitCode"], "inputs", len(inputs), "executable records", len(artifacts))
|
||||
return {"runs": len(runs), "recordedFailures": sum(spec["exitCode"] != 0 for spec in runs.values()),
|
||||
"logicalFiles": len(logical), "storedMembers": len(stored), "sourceCapsulePairs": len(seen_capsules),
|
||||
"captureHelpers": helpers, "sourceProofs": sum("sourceProof" in spec for spec in runs.values())}
|
||||
|
||||
|
||||
def verify_package(root):
|
||||
files = parsed((root / "files.json").read_bytes())
|
||||
need(set(files) == OUTER_FILES, "outer file inventory")
|
||||
for name, expected in files.items():
|
||||
need(valid_pin(expected) and pin((root / name).read_bytes()) == expected, "outer hash: " + name)
|
||||
manifest = parsed((root / "manifest.json").read_bytes())
|
||||
with zipfile.ZipFile(root / "evidence.zip") as archive:
|
||||
inventory(archive, manifest["members"])
|
||||
return verify_data(manifest, archive.read)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("package", nargs="?", type=Path, default=Path(__file__).resolve().parent)
|
||||
args = parser.parse_args()
|
||||
print("PASS offline consistency:", verify_package(args.package))
|
||||
print("Historical receipts only; no source/binary execution or native acceptance claim.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,56 @@
|
||||
# Scenario group adapter: independent design critique
|
||||
|
||||
2026-09-24. Bounded read-only critique in `C:/ar-scenario-9e8r6t`, identified by the parent as source `7d3d7b2` on the combined base. Read the working agreement, user specification, multiple-selection contract, Scenario reorder policy, proposed adapter design and relevant actual source. No Cargo, Git, network, GUI or production changes. This file is the reviewer's only output. Findings below concern the proposed adapter; they do not negate or accept the separately tested pure planner.
|
||||
|
||||
## Assessment
|
||||
|
||||
The representation-specific selection Scope plus full Scenario Context envelope is the best-supported part of the proposal. It reuses existing exact-target selection while preserving saved/derived identity and visit admission. The saved-view one-shot reorder arm is **not yet justified as the preferred interaction**. It separates semantics correctly, but adds an interaction state without solving the more consequential problem: a successful semantic reorder can leave the authored messages visibly in their old positions.
|
||||
|
||||
The user's requirements emphasize direct graphical editing, low cognitive load and avoiding a bloated interface (`PROJECT_GOAL.md`, user-owned general description/UI). The accepted contract preserves saved presentation records during semantic reorder. Both constraints must be satisfied; neither a successful planner nor correct preserved coordinates establishes understandable interaction.
|
||||
|
||||
## 1. Resolve observable success before choosing the saved-view gesture
|
||||
|
||||
**Material design gap.** The proposed arm says “change shared order; saved positions stay unchanged,” but does not specify how the engineer sees the resulting semantic order when saved geometry stays put. `scenario_layout.rs:635` restores exact saved send/receive positions; `:437` only diagnoses source-order crossings. `behavior.rs:1576` puts insertion slots near those painted anchors and `:1807` chooses the nearest physical slot. A saved canvas is therefore not necessarily a visual ordering of semantic destinations.
|
||||
|
||||
Concrete falsifier: use four independent asynchronous messages with authored Y values A=200, B=300, C=400, D=500 and semantic order `[A,B,C,D]`. Select C then A, then reorder before D. The planner correctly returns `[B,A,C,D]` while all four authored Y values remain exact. The principal arrows can remain in the same places; the operation has changed semantics without the visual movement the user may expect. A frozen model comparison is necessary but cannot establish that this interaction is understandable.
|
||||
|
||||
There is also concrete misleading copy: `scenario/workspace.rs:726` displays “The Scenario order is unchanged” for a `SourceOrderCrossing` diagnostic. A successful semantic reorder can itself create that diagnostic. Keep action-specific “order stays unchanged” in spacing-preview feedback if accurate, but make the general presentation notice neutral, e.g. “Saved positions differ from Scenario order.” The existing order shelf already distinguishes stored positions from automatically positioned items (`workspace.rs:363`); reuse that distinction. “Authored positions are preserved” is more precise than promising every painted position stays unchanged.
|
||||
|
||||
**Required before adapter acceptance:** a visible exact destination (“before D in Main sequence”), a preview of the resulting semantic order, and an observable successful result after commit that does not require opening source to infer what happened. Candidate geometry may also become fallback after a valid semantic reorder while saved records remain exact; state whether that is allowed, and show the outcome rather than presenting an unexplained representation change as success. Do not expand the semantic planner into a renderer validator solely to avoid this product decision.
|
||||
|
||||
## 2. Compare a direct handle and operation-local order preview; the arm is not the only bounded alternative
|
||||
|
||||
The initial comparison omits an existing affordance: `behavior.rs:1827` defines a reorder handle, painted only in Derived mode at `:2545`. A group-aware, explicitly labelled handle could start semantic reorder directly while ordinary saved message/header drags retain spacing semantics. It avoids an armed interval, an extra click, and the need to remember that the next body drag has changed meaning. It still needs clear group scope and a keyboard/contextual equivalent. Merely enabling the current singleton handle would be incorrect.
|
||||
|
||||
| Candidate | Simplicity and result visibility | Recommendation |
|
||||
| --- | --- | --- |
|
||||
| One-shot arm followed by saved body drag | Explicit intent, but requires an extra mode and leaves physical/semantic order ambiguity. | Retain as an experiment, not the accepted preference. |
|
||||
| Explicit group reorder handle in the saved view | One direct gesture and no persistent arm; saved spacing remains familiar. Saved geometry alone still does not show the changed order. | Strong simpler candidate, paired with a semantic order preview. |
|
||||
| Operation-local automatic/order preview | Captures the current saved Context and exact set, presents resulting sequence order while retaining authored records and the saved view underneath. Makes destination and outcome observable. | Prototype alongside the handle; prefer a compact graphical/neighbor preview over another large homogeneous list. |
|
||||
| Navigate to Automatic arrangement | Reuses existing interaction, but contractually clears group selection and forces reselection. | Acceptable explicitly limited first delivery, not full saved-view group acceptance. |
|
||||
|
||||
An operation preview is a pending command against the captured saved selection, not a silent representation navigation. Keep the actual saved representation/visit and live group intact; use an explicit preview/gesture lifetime, and execute only the exact captured set. Do not change the arrangement picker or transfer a group across surfaces by exception. Derived preview geometry must not retroactively authorize originally hidden or unsupported selected targets. If the chosen preview becomes a separately navigable editor, define its input identity explicitly instead of pretending it is the unchanged saved canvas.
|
||||
|
||||
**Bounded comparison task:** select noncontiguous A/C in the saved view, reorder before D, identify the new sequence without consulting source, and perform the next ordinary spacing operation correctly. Compare handle-plus-preview and arm-plus-preview for errors, unexpected movement, extra recovery steps and discoverability. Verify preserved authored records and exact `[B,A,C,D]` independently. A reversible prototype can resolve this choice after publishing the pure planner; no production arm is needed to obtain that evidence.
|
||||
|
||||
## 3. Add an explicit gesture-retirement invariant
|
||||
|
||||
**Missing lifetime contract, not a demonstrated implementation defect.** The proposed state list (`.local/scenario-group-adapter-design.md:41`) captures Context, selection Snapshot and tool/arm state, but does not name an App-admitted gesture identity or specify which retirement advances invalidate delayed packets. `diagram_selection.rs:170` detects context and selection-generation changes; it does not identify two gestures on the same unchanged selection. Completing a no-op or cancel followed by rearming can leave all those values equal unless an additional lifetime changes.
|
||||
|
||||
Specify exactly one solution: an App-owned token/generation for each admitted gesture/arm, or mandatory captured-context invalidation on every relevant retirement. A boolean “armed” check is insufficient. Rejected old packets must not disarm a newer operation. Distinguish stationary click, changed commit, no-op, refusal, Escape, outside release and focus/camera loss; each consumes only its own matching capability. Retirement may change transient gesture state without changing model/source/history/selection. Existing spacing already provides a useful bounded pattern: `spacing/controller.rs:17` plus token-bearing events, and `app/scenario_spacing.rs:26` matches the active token before mutation and advances epoch on Finish at `:52`.
|
||||
|
||||
Also strengthen the spacing bridge requirement at design line 56. Refusing Begin only while the live set is a group is weaker than matching captured selection identity: a delayed singleton Begin after a group was created and reduced again can pass that test. Either snapshot-match Begin/Finish, or prove and test epoch retirement for **every** selection generation change, including add/remove of a secondary member that leaves the legacy primary unchanged. Do not rely only on `self.selected` or group size.
|
||||
|
||||
Falsifiers: cancel A, start B with the same model/selection, deliver A's release/cancel; finish an unchanged A, rearm B, deliver duplicate A; create/remove a secondary selection member before delivering the old spacing Begin. B and the restored exact selection must remain intact, with no command or history effect from A.
|
||||
|
||||
## 4. Make the promised origin-return no-op explicit
|
||||
|
||||
The proposed evaluator promises “out/back origin remains threshold-crossed Noop” (design line 71). The planner cannot establish that: `group_reorder.rs:138` only receives a Slot and returns no-op for a selected anchor or unchanged transformed list. `behavior.rs:2043` latches threshold crossing, while `:1807` maps the final physical position to the nearest slot. A press on the body at a row's Y is not generally nearest its own slot, which is offset by 42 at `:1582`; saved anchors may be arbitrarily close or crossed. Returning to the original pointer can therefore choose an unselected neighbor and compact/move a noncontiguous group.
|
||||
|
||||
If origin-return no-op is intended, define it in the gesture adapter before slot planning: preserve the ever-crossed flag for selection behavior, but classify return to the captured origin using an explicit screen-space tolerance/axis policy. Admit the envelope and baseline first, then return no-op without executing. Test actual message-body, label and handle press positions in closely spaced saved geometry. Otherwise remove that stronger promise and document the destination-based behavior; do not assume the selected-anchor rule supplies it.
|
||||
|
||||
## Sound constraints to retain
|
||||
|
||||
Keep full representation/visit in the Scenario envelope and representation identity in the generic Scope; no global generic-context redesign is justified. Use actual presented typed visibility, preserve whole-set refusal and original primary, and close raw order-row/menu/singleton bypasses before showing an operational group. Clear groups on actual surface navigation as required. The current nearest-displayed-slot-then-parent-admission rule is coherent, but a preview must name the semantic destination when physical order differs.
|
||||
|
||||
These are source/design conclusions only. Native focus/Shift behavior, preview stability, event ordering, readability, exact history and actual usability remain unverified. The pure planner can be delivered as a bounded milestone while the saved-view interaction remains an explicit open decision.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Scenario group adapter design against the combined source
|
||||
|
||||
Read-only design review for issue 29, 2026-09-24. Inspected the actual combined source in C:/ar-scenario-9e8r6t while the parent's frozen planner gate runs. This report is the only written file. No code/tests, Cargo, Git, network, or GUI operations. References below are repository-relative current-source locations, not evidence of runtime acceptance. The parent owns synthesis, integration and publication.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Reuse the existing shared Selection/Snapshot data structure, but deliver Scenario selection and semantic reorder through a Scenario-specific event envelope containing the full `scenario::Context`. Map the shared selection surface to the actual representation: Derived => `Surface::Scenario(owner)`; Saved => `Surface::Saved(diagram)`. Keep `Representation` and `visit` in the Scenario envelope and captured gesture/menu; do not extend every generic Context or change diagram-usage Surface globally.
|
||||
|
||||
Preserve the current saved-view default interaction: header/message drags mean authored spacing. Add an explicit, clearly labelled one-shot **Reorder selected occurrences** action for semantic group reorder in a saved representation. It arms only the accepted exact selection for the next drag and visibly explains that shared order changes while saved positions remain. Derived Select can retain its ordinary semantic-reorder behavior. A saved group body drag without that explicit action must never silently reorder, move only its primary, or infer group spacing: retain the set and explain the unavailable group spacing operation after threshold crossing. Singleton saved spacing continues through its existing controller.
|
||||
|
||||
This is a recommended bounded product/design decision, not an already accepted user requirement. It follows the existing distinction between saved presentation edits and semantic order, avoids forcing a representation switch merely to move a group, and preserves the existing default gesture.
|
||||
|
||||
## What the combined source actually does
|
||||
|
||||
1. **Shared selection has no active Scenario adapter yet.** `app/multiselect.rs:91` boundary includes document/revision/page/active_diagram/legacy selected/placement, State identity and Process context/address. `:105` returns Process or State contexts on Behavior; it never returns a Scenario selection context. `:122` visibility similarly handles Process and projected saved Architecture/State; `:223` reconciliation falls into those same branches. `diagram_selection.rs:141` reconciles by Scope and exact visibility, while `:179` matches full generic context, generation, ordered membership and primary. `Surface::Scenario` and `Target::ScenarioObject` exist as diagram-usage identities, but their existence does not wire live group input.
|
||||
2. **Scenario already has the missing lifetime authority.** `behavior/scenario.rs:117` Context contains document, owner, exact Representation, visit, revision and epoch. `:391` enter_representation generates a fresh visit on representation change or reentry; `:422` leave invalidates it. Thus A -> B -> A gets a different visit even when the Scenario owner and final saved diagram are the same. `app.rs:2073` constructs the authoritative current context. `app/scenario_diagram.rs:6` entry updates active_diagram and canvas_epoch, while reconciliation retains an invalid Saved identity rather than silently switching to Derived.
|
||||
3. **The top-level update order matters.** `app.rs:1374` rejects generic selection envelopes early. `:1483` routes Spacing directly to its handler and returns; `:1501` rejects stale Scenario document/owner/representation/visit before reveal/allocation/camera reconciliation. Only specified passive events relax epoch. `:1553` onward can retire navigation/gesture state, and `:1603` performs shared selection reconciliation after ordinary dispatch. New Scenario group packets must reject both the full Scenario envelope and shared snapshot before these side effects. Adding only an inner-handler check is too late.
|
||||
4. **Presentation is the actual visibility authority.** `scenario/workspace.rs:78` obtains either frozen spacing paint or `presentation::present` for the exact representation. At `:138` it configures the actual Scene, coordinate capability and full spacing stamp. A saved view can display fallback/unavailable geometry with different capabilities. Do not use `Scene::sequence` or ancestor pointers as current saved-view visibility; those are derived semantic geometry and can authorize a target absent from the actual scene. Add a small Scenario accessor returning the visible typed hit targets from the exact displayed Scene. Keep visibility separate from reorder eligibility so lifelines, operands and mixed sets can be selected/inspected and then refused as a complete movement set.
|
||||
5. **Input currently knows only a singleton.** `behavior.rs:1264` SequenceInput has no modifier state or shared Snapshot. `:1290` captures one occurrence/parent; `:2293` releases either Select or singleton Reorder. Scene keeps rows/slots plus boxes/lines/activation/lifecycle primitives. `:1841` hit_item deliberately resolves marker, topmost titled box/header, exact line, activation's Call, label, then tolerant items and lanes. Reuse this exact winner for Shift and menu selection; do not build a second hit order. `:1807` picks the nearest slot first and only then checks parent; preserve that policy and expose a reason instead of choosing a farther legal slot.
|
||||
6. **Saved spacing already owns precise roles.** `behavior.rs:2149` branches on Saved before semantic reorder. Only LifelineHeader and MessageBody/MessageLabel start spacing; lifecycle markers, activation segments, guides, fragments and other boxes select only. `scenario/spacing/controller.rs:17` Stamp is full Context plus camera_epoch; sessions additionally own token, initial screen/scale/pan and frozen model. `app/scenario_spacing.rs:20` checks context, camera and active token before any mutation. Do not broaden these role permissions merely because a target is in a group.
|
||||
7. **Passive modifier support is already delivered by the wrapper.** `behavior/canvas_workspace/viewport.rs:152` observes ModifiersChanged before keyboard focus gates and forwards it to the child; `:361` replays held modifiers before a pointer press, including after child rebuild. Scenario currently ignores it except Delete's event modifiers. Add a local modifier field updated before gesture admission, clear on window unfocus, and never publish a focus/camera command for passive modifier events. The existing mounted regression is `scenario/workspace/tests.rs:7`.
|
||||
8. **Other controls still bypass group intent.** `scenario/workspace.rs:913` recursively builds order rows whose labels emit raw `Message::Select`; arrows at `:939` emit raw `Message::Command(MoveStep)`. `app.rs:2277` handles singleton Remove; `:2511` singleton Reorder; `:3557` applies raw commands (including MoveStep) after apply(). Delete has a shared group guard at `:3605`, but its non-Process message offers Hide, which is inappropriate for Scenario occurrences. All need Scenario-aware handling before a displayed group can exist.
|
||||
9. **Menus are not yet Scenario-capable.** `model_menu.rs:11` Request has canvas/process but no Scenario context. `:112` target_matches rejects selection targets without those contexts and does not admit ScenarioObject. `:171` Actions-trigger captures Process/State context only. `:264` selection menu rows publish generic packets and treat non-Process groups as placements. Merely populating diagram_selection would therefore make the existing Actions trigger fail or offer wrong actions; wire the menu deliberately.
|
||||
|
||||
## Lifetime alternatives
|
||||
|
||||
| Candidate | Concrete implementation | Tradeoff and decision |
|
||||
| --- | --- | --- |
|
||||
| Extend generic Context/Scope globally | Add optional Scenario representation/visit to Context, or introduce a new generic SelectionSurface variant that holds them, then update every constructor/matcher/test in Diagram, State, Process and menus. | Can express everything, but pushes a Scenario-only lifecycle into unrelated adapters and conflates usage identities with input visits. No current requirement justifies that migration. Reject for this increment. |
|
||||
| Scenario event envelope plus existing generic Snapshot | `CanvasEvent::Selection { context: scenario::Context, event: ScenarioSelectionEvent }`; mutation events carry the existing immutable Snapshot. A Scenario conversion maps actual representation to an existing Surface. Match full context and Selection::matches before side effects. | Reuses the already authoritative visit mechanism, needs only a bounded Scenario adapter and small dispatch branches, preserves existing generic invariants. **Preferred.** |
|
||||
| Owner-only Scope plus canvas_epoch alone | Convert full Scenario Context to `Surface::Scenario(owner)` and trust epoch to reject navigation; use bare generic selection messages. | Epoch normally changes today, but representation identity is erased, surface changes fail to clear membership, and a future/passive path can weaken the guarantee. It also leaves menus without full visit admission. Reject as an implicit assumption. |
|
||||
|
||||
The recommended Scope mapping means changing saved A to saved B or Automatic clears the set, as required by the current multiselect contract's “changing surface clears the set.” Legacy single-object inspection can survive a same-owner representation switch, as it already does, then seed at most one visible target on the new surface. Do not silently preserve the whole group across representation navigation. This corrects a tempting owner-scoped shortcut: Scene representation is a distinct rendered surface even when semantic membership happens to match.
|
||||
|
||||
Keep visit out of Selection scope so routine context refreshes do not redefine logical membership. The full Scenario envelope is still mandatory: after A -> B -> A, an old A packet cannot pass even if the user recreated identical membership/primary and the shared surface equals A. Clear selection on leave/navigation under the existing contract; a new active visit also retires any gesture, menu or explicit reorder arm.
|
||||
|
||||
## Minimal integration shape
|
||||
|
||||
- Add `scenario_context` conversion in multiselect.rs, with Scenario dispatch before the generic Saved projected-view branch. Include `self.scenario_context()` in multiselect_boundary (or the specific owner/representation/visit identity plus existing revision). Route Scenario visibility, reconciliation and primary inspection to a bounded `app/scenario_multiselect.rs` module, patterned after process_multiselect without importing qualified Process semantics.
|
||||
- On reconcile, preserve an existing exact group when legacy `selected` still names its primary and the representation Scope is unchanged; prune only after ordinary accepted model changes. If primary disappears, choose the shared Selection's survivor; never retarget by definition. Explicit unrelated selection replaces the set. Inspection sets selected to the ScenarioObject ID, clears placement/bend/attachment/navigation focus as appropriate, refreshes the primary draft, and does not navigate representations. Keep property edits primary-only.
|
||||
- Scene gets a captured Scenario selection envelope and an immutable model Arc (or other existing accepted-document snapshot access) for pure Prepared construction. SequenceInput group state owns full context, Snapshot, pressed target, screen origin, bounds, pan/scale, camera_epoch, threshold-crossed flag, Prepared Result, last exact Slot, and cached Plan/refusal. These fields are local to Scenario, not a new shared gesture framework.
|
||||
- Shift toggles the exact hit and captures the event before either spacing or reorder starts. Plain unselected press replaces. Plain press on a selected member freezes the complete set and primary; stationary release collapses to that member; once threshold is crossed, refusal, cancel, origin return and selected-anchor Noop keep both unchanged. Do not inspect the pressed member before knowing the outcome, because Details layout changes can change hit geometry. Background clear can coexist with the existing background pan only if it does not capture the semantic hit path.
|
||||
- For each changed nearest Slot, call the frozen Prepared planner once and cache its result; exact same parent/anchor identity is the cache key. Preview must show selected count, destination and no-change/refusal, not a singleton ghost that implies only the primary will move. Fragment interior slots can produce a deliberate operand refusal.
|
||||
- At release, first compare full current Context (including representation/visit), shared Snapshot generation/membership/primary, exact current visible membership, camera lifetime, bounds and tool/arm state. Refuse before any reconcile can prune the old set. Recompute using the same captured project/selection or prove current model equals that accepted revision; execute one admitted command through Document only for Change. Do not use `apply()` to silently apply pending forms during this operation. On success restore the original primary before shared reconciliation: App::execute at `app.rs:1033` receives SetSteps' parent as the core selected result and calls sync; blindly trusting that returned primary would collapse or misinspect the group. `sync` does not itself call reconcile_multiselect, so restore/inspect immediately before the outer reconciliation. Noop bypasses execute and navigation/reveal retirement.
|
||||
- Add Scenario group envelope checks alongside the existing early checks in App::update, not just CanvasEvent::context. Any bare generic DiagramSelection packet while Scenario is active should refuse unless it entered through the full Scenario wrapper; otherwise generic Choose/Clear remains an unguarded representation/visit channel. This applies equally to menu-generated selection packets.
|
||||
|
||||
## Saved spacing versus explicit semantic reorder
|
||||
|
||||
Two viable bounded choices were compared:
|
||||
|
||||
1. **Require switching to Automatic arrangement.** Existing representation picker (`workspace.rs:210`) already exposes it, Derived already reorders, and saved spacing remains untouched. This has the least new input mode but switching rendered surfaces must clear the group under the accepted selection contract, requiring reselection. Preserving a group automatically to make this shortcut convenient would need a contract amendment. It also changes the working view when the user only asked to change order.
|
||||
2. **Explicit one-shot semantic reorder in the current saved view (preferred).** Show a selected-count action such as “Reorder selected occurrences”; on activation capture full Context + Snapshot and validate the whole planner baseline. Display “Drag a selected occurrence to change shared order; saved positions stay unchanged” and Cancel. Only this armed gesture can outrank saved spacing. Complete or cancel disarms it; changing selection, tool, view, visit, revision, focus/camera or pending state also disarms. The default remains Select / move messages and lifelines. This adds one bounded Scenario state rather than changing meaning based on group size or Shift (Shift remains selection only).
|
||||
|
||||
For singleton normal saved drags, preserve the existing header/message spacing precedence and exact hit-role checks. For a multi-selection normal saved drag, capture a selection-owned pending press: stationary release follows the ordinary collapse rule; threshold crossing reports “Moving saved positions as a group is unavailable; choose Reorder selected occurrences to change sequence order.” No spacing Begin should be dispatched for only its primary. Unsupported sets (including lanes, Branch headers, cross-parent members) remain selectable but the explicit reorder action refuses the complete set with the planner reason. Do not expand role eligibility for activation/lifecycle hits in the ordinary saved-spacing mode.
|
||||
|
||||
The `scenario_spacing` early return bypasses the outer reconciliation. Once group selection exists, its Click/Commit paths must deliberately reconcile/inspect accepted singleton identity or use the Scenario selection adapter, and unchanged/failed spacing must preserve the captured membership/primary. Capturing a selection Snapshot with the spacing interaction or refusing Begin when the live set is a group prevents a queued singleton spacing packet from becoming a partial group edit after a selection change. Full stamp/token alone does not include selection generation. Do not gratuitously reset spacing camera/preview ownership by routing its packets through ordinary navigation.
|
||||
|
||||
A simpler first implementation may choose option 1 explicitly, but must label group reorder as Automatic-view-only and require reselection; it cannot claim full saved-view group operation acceptance. The preferred option provides the intended semantic action without changing presentation records or the active representation.
|
||||
|
||||
## Controls and menus that must be closed before enabling group input
|
||||
|
||||
- Add full Scenario Context (and group Snapshot where appropriate) to contextual Request and validate it in Opened::new/valid plus target_matches. Admit only exact ScenarioObject with matching object, no placement, and actual represented visibility. Actions-trigger and right-click should capture without replacing; Add/Remove/Inspect/Select-only publish Scenario-wrapped events. Group menu offers the semantic reorder action and explains aggregate removal/Hide limits; no placement Hide.
|
||||
- Replace Scenario order-row arrows with captured Scenario actions, or disable them for groups and add an early refusal for stale `Message::Command(MoveStep)` while a Scenario group is current. Raw index packets are not safe against changed order/visit; a captured parent and occurrence/destination identity is better even for singleton arrows. Row selection should be captured, not delayed raw `Message::Select`; otherwise old Details rows can navigate after a representation switch. Group row arrows must never apply to one displayed member.
|
||||
- Mirror Process early singleton guards for Scenario Select/Remove/Reorder, including stale menu/Delete packets, before reveal/camera mutation. Normal allowed group selection changes arrive only in the new envelope. Existing aggregate removal is separate: reject group Remove/Delete with an accurate reason, not “Hide selected.” Keep singleton deletion's existing review.
|
||||
- Guard new selection, arm and reorder against source/property/behavior drafts, modal/rename/busy/read-only state, coordinate-unavailable fallback and wrong tool. Allow safe visual selection only where existing capabilities permit; a fallback hit is not movement authorization. Keep open-definition navigation and representation changes explicit boundaries.
|
||||
|
||||
## Discriminating acceptance before integration
|
||||
|
||||
- A/B/A same-owner saved visits: stale Choose, Clear, Collapse, menu invoke, arm, reorder release, singleton Remove/MoveStep, and spacing Finish are inert before model/source/history/selection/primary/camera/reveal changes; include same-membership recreation and same owner/revision cases.
|
||||
- Native/mounted first Shift press from text/Properties focus and after busy child replacement; release/repress of Shift; no focus theft from passive modifiers. Hit the same subject at message body/label, activation, lifecycle marker, Fragment header and Branch header; compare actual hit identity with selection and support/refusal.
|
||||
- Noncontiguous C/A selection and pressed-member variants preserve semantic order and original primary on drag; stationary click collapses, selected-anchor Noop keeps redo, out/back origin remains threshold-crossed Noop, Escape cancels before clearing, and rejected member sets never move a subset.
|
||||
- Saved default header/message singleton spacing still updates only coordinates; normal group drag refuses without changing any coordinate/order or primary. Armed saved semantic drag changes only SetSteps and preserves both affected/unrelated authored layouts; default spacing resumes afterward. Camera/visit/selection changes cancel both kinds of captured input as intended.
|
||||
- Stale toolbar/index commands and context-menu Add/Remove/Inspect do not bypass envelope admission; property edits address only primary. Pending forms remain untouched on group refusal. Exact saved/derived surface changes clear group membership as specified.
|
||||
- Use the current planner's complete model/source/history oracle for successful App operations, plus exact selection/primary and zero-history refusal/no-op checks. Reuse the existing spacing/controller and mounted passive-focus regression evidence; rerun affected workflows after changes. Source design alone establishes none of these runtime outcomes.
|
||||
@@ -0,0 +1,111 @@
|
||||
# Scenario planner delivery record: bounded independent review
|
||||
|
||||
2026-09-24. Reviewed the delivery documentation and four closed raw gates while strict lint was still outside this review's acceptance scope. HEAD independently read as `7d3d7b23e768b66b93b608bcc6b4fb1e7f972f89`, parent `8ddfadf440b5c208b1adb6b211a97ba0c99cff04`. Only this report was written. No Cargo, Git mutation, network, GUI, package assembly or publication was performed.
|
||||
|
||||
## Result
|
||||
|
||||
No material finding in the reviewed planner claims, test counts, source identity, copied reviews or available relative documentation links. The record accurately limits delivery to the semantic planner foundation and its singleton wrapper. It does not claim a new Scenario group App/canvas adapter, aggregate removal, native gesture acceptance, full-workspace verification, Linux verification, packaged application or release acceptance.
|
||||
|
||||
The strict lint result and the not-yet-assembled checks package remain explicit delivery gates outside this review. The prose “are packaged” and checks/README link are prospective at this snapshot: the parent has explicitly reserved package construction/verification until lint closes. Do not publish that statement with a missing package. This is the known excluded boundary, not a finding against the four closed gates.
|
||||
|
||||
## Exact records inspected
|
||||
|
||||
Documentation:
|
||||
|
||||
- `harness/scenario-group-planner-20260924.md` (complete record).
|
||||
- `harness/scenario-group-reorder-policy-20260924.md` (complete amended policy, including adapter decision boundary).
|
||||
- `harness/multiselect-contract-20260923.md` (linked contract and relative-link targets).
|
||||
- `harness/scenario-group-planner-evidence-20260924/reviews/planner-review.md` (complete initial review and expanded-test follow-up).
|
||||
- `harness/scenario-group-planner-evidence-20260924/reviews/adapter-design.md` (reused prior full read; verified SHA-256 exact copy of the already reviewed `.local/scenario-group-adapter-design.md`).
|
||||
- `harness/scenario-group-planner-evidence-20260924/reviews/adapter-critique.md` (complete critique).
|
||||
|
||||
Source:
|
||||
|
||||
- `crates/oa-desktop/src/behavior/scenario/group_reorder.rs`, `group_reorder/tests.rs`, and singleton wrapper in `behavior/scenario.rs`; previously reviewed full source was checked against current HEAD with no working-tree delta. Current test declarations and planner admission/command sites were reread.
|
||||
- Exact parent identity and the `897d9f04` -> `8ddfadf4` diff in `crates/oa-desktop/src/app/allocation_drag_tests/group.rs`, which adds `use iced::widget::text_editor;`.
|
||||
|
||||
Closed runs under `.local/scenario-group-gates/`:
|
||||
|
||||
- `planner-focused-1/{result.json,source-proof.json,stdout.log,stderr.log,inputs.json,inputs.zip}`; input map/archive/log bytes independently hashed and sized against result pins. Parsed compile diagnostics, no test execution result.
|
||||
- `planner-focused-2/{started.json,result.json,source-proof.json,stdout.log,stderr.log,inputs.json,inputs.zip}`; input inventory parsed, complete executed test names extracted, raw files independently hashed/sized.
|
||||
- `scenario-regressions-1/{result.json,source-proof.json,stdout.log,stderr.log,inputs.json,inputs.zip}`; complete test status lines independently counted, ignored name/reason inspected, raw files independently hashed/sized.
|
||||
- `format-1/{result.json,source-proof.json,stdout.log,stderr.log,inputs.json,inputs.zip}`; zero exit and empty logs inspected, raw files independently hashed/sized.
|
||||
- `.local/scenario-group-gates/gate.py` and `source-proof.py`; the captured gate helper hash matches the current 3778-byte helper. The relevant source-comparator implementation in `.local/process-source-evidence-tools/verify.py` was read. Source-proof receipts were checked against result hashes, but the 804-file Git-blob comparator was not independently rerun in this bounded review.
|
||||
|
||||
The three successful runs all name `7d3d7b23...`, report `inputsUnchanged: true`, and have the same 804-entry input-map hash `e307e14fc0ec93bc6ffedd8f7155cdb398651e31cb11cb68ff5ae8160fd507d6`. The map contains 71 patched iced_widget inputs and both named explicit compile-time fixtures. The three archives have the same SHA-256 `2d208776732619a1a7bdd2c7275b74db5efc9e3bf5606723afc8cb3c225027fe`. The failed run has its distinct `d79a5ed...` source and map; no accidental substitution of the passing source was observed.
|
||||
|
||||
All four input-map/archive/stdout/stderr byte lengths and SHA-256 values match their result records. All four source-proof receipts pin their actual result bytes and identify their expected commits. The two successful test runs name the same 160743936-byte test executable and SHA-256 `48d0ee9505573e9aabd5d175b159275e12b75ddd7f261f89bab7b71106df14c3`; the first compilation is fresh=false and the broader run reuses it fresh=true. This supports the claimed run identity; it is not an independent compiler-environment reproduction.
|
||||
|
||||
## Executed names and outcomes
|
||||
|
||||
`planner-focused-1`: exit 101; three E0433 diagnostics for the missing `text_editor` module at allocation_drag_tests/group.rs lines 226, 229 and 230. No test result or executable artifact. The delivery record correctly preserves this as a compilation failure rather than an executed-test result.
|
||||
|
||||
`planner-focused-2`: raw result is 20 passed, 0 failed, 0 ignored, 1527 filtered out. Exactly 12 names have prefix `behavior::scenario::group_reorder::tests::`; these match all 12 current `#[test]` declarations one-for-one:
|
||||
|
||||
- admission_rejects_every_invalid_member_list_and_destination_before_noop
|
||||
- call_reply_moves_as_explicit_stable_pair_and_invalid_baseline_refuses_noop
|
||||
- conditional_delete_rejects_moving_an_earlier_use_after_the_fragment
|
||||
- exclusive_operand_move_is_legal_but_parallel_baseline_is_invalid
|
||||
- nested_fragment_descendant_reply_is_validated_and_subtree_preserved
|
||||
- nested_lifetime_and_opaque_local_bindings_are_checked_without_expansion
|
||||
- repeated_reply_warning_does_not_block_independent_sibling_reorder
|
||||
- root_and_operand_use_semantic_block_order_with_exact_history
|
||||
- selected_anchor_and_exact_noop_preserve_redo_and_frozen_snapshot
|
||||
- semantic_reorder_preserves_affected_and_unrelated_authored_layouts
|
||||
- singleton_wrapper_noop_preserves_existing_redo_and_source
|
||||
- unlisted_warning_child_is_preserved_but_cannot_be_selected
|
||||
|
||||
Exactly eight names have prefix `behavior::topology::group_reorder::tests::`, all successful:
|
||||
|
||||
- process_group_columns_accept_all_local_card_kinds_and_different_heights
|
||||
- process_group_columns_end_gaps_and_selected_anchor_noops
|
||||
- process_group_columns_full_order_admission_precedes_selected_anchor_noop
|
||||
- process_group_columns_legacy_exchange_order_refuses_cross_rank_group
|
||||
- process_group_columns_parallel_links_keep_exact_ranks_and_relation_records
|
||||
- process_group_columns_refuse_entire_invalid_typed_sets_before_noop
|
||||
- process_group_columns_renderer_omits_links_to_unlisted_nodes_without_panicking
|
||||
- process_group_columns_reverse_noncontiguous_selection_preserves_project_and_history
|
||||
|
||||
`scenario-regressions-1`: 251 discovered; independent raw status-line count is 250 ok and one ignored, agreeing with the final summary (0 failed, 1296 filtered out). Ignored: `behavior::scenario::spacing::controller::timing_tests::saved_spacing_capture_prepare_20_lanes_1000_messages_3_views`, with the logged opt-in diagnostic/no-latency-threshold reason. The report correctly says filtered counts overlap and must not be added.
|
||||
|
||||
`format-1`: exit 0 with empty stdout/stderr and unchanged input map. No inferred test-execution claim.
|
||||
|
||||
Rust/Cargo 1.98.1, stable-x86_64-pc-windows-gnullvm, CARGO_BUILD_JOBS=1 and CARGO_INCREMENTAL=0 are directly recorded. The additional LLVM-MinGW distribution version in the prose is parent-provided environment context, not independently established by these four gate records: they do not capture PATH/linker identity. This does not undermine their recorded outcomes; retain that distinction if describing reproducibility strength.
|
||||
|
||||
## Claim and link readback
|
||||
|
||||
Planner behavior matches source: complete typed/list/reachability checks precede baseline validation, selected-anchor/no-change returns occur only after that preparation, semantic block order comes from the original list, and changes emit only a validated SetSteps. The whole-Project oracle, source parse/format, bundle reopen and exact history helper support the stated successful transformation coverage. Singleton no-op is separately executed through Document with existing redo. The report does not turn these into App membership/primary or gesture guarantees.
|
||||
|
||||
The copied original reviews retain their historical base/line boundaries and read-only limits; the main record correctly states that the parent subsequently executed the checks. Planner-review and adapter-design copies match their local originals byte-for-byte. The independent critique's observable-result, gesture-retirement, spacing selection ABA and origin-return issues are reflected in the amended policy as future adapter obligations. The one-shot saved reorder arm is explicitly unresolved rather than silently accepted; comparing it with a handle/order preview preserves the critique.
|
||||
|
||||
Every checked relative Markdown link from the delivery record, policy and multiselect contract exists except `scenario-group-planner-evidence-20260924/checks/README.md`, the expressly pending package. Remote issue/evidence links were not fetched under this task's no-network scope. Copied reviews use historical plain-text source/local paths as provenance, not a false claim that those local paths are accessible to remote peers.
|
||||
|
||||
The parent may proceed to final lint-result incorporation and independent package verification. This report accepts neither the pending strict-lint gate nor package contents/publication, and does not close issue 29's adapter/removal work or alter issue 37's frozen release.
|
||||
|
||||
## Final extension: closed lint and assembled offline package
|
||||
|
||||
The parent subsequently extended this review to the closed strict-lint run and assembled checks package. This section supersedes the earlier pending-lint/package boundary; remote publication, App/native behavior and release acceptance remain outside scope.
|
||||
|
||||
Inspected additional records:
|
||||
|
||||
- `.local/scenario-group-gates/strict-lint-1/result.json`, `source-proof.json`, `stderr.log`, and the stdout build-finished record.
|
||||
- `harness/scenario-group-planner-evidence-20260924/checks/{README.md,verify.py,files.json,manifest.json,evidence.zip}` through source inspection, direct hashing and offline execution.
|
||||
- Updated `harness/scenario-group-planner-20260924.md` and `harness/scenario-group-planner-evidence-20260924/selfcheck-1.log`.
|
||||
|
||||
Strict lint closed at exit 0, `inputsUnchanged: true`, 804 inputs, source HEAD/proof `7d3d7b23...`, the same passing input-map/archive pins, and raw `build-finished` success=true. Its recorded command is exactly `cargo clippy -p oa-desktop --all-targets --message-format=json -- -D warnings`. The updated delivery table accurately reports warnings denied; no further Cargo was run by this reviewer.
|
||||
|
||||
Independently measured archive: 6,416,449 bytes, SHA-256 `211530e47aec6254ee1476081352139497091aae6fef249274c20aa1808b741f`, matching the delivery record and parent handoff.
|
||||
|
||||
After reading verify.py, executed only the standalone offline verifier with the full bundled Python runtime:
|
||||
|
||||
`C:/Users/User/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/python.exe -B harness/scenario-group-planner-evidence-20260924/checks/verify.py`
|
||||
|
||||
Exit 0. Reported five runs, one recorded failure, 43 logical files, 37 stored members, two source capsule pairs and five source proofs. It verifies outer pins, exact archive member names, nested source archive contents, recorded commands/process/result relationships, helper/comparator/proof byte identities and executable-record relationships. Historical archived helpers, source and binaries were not executed or extracted. The comparator receipts remain evidence of the parent's Git comparison, not a rerun of that comparison by the offline verifier.
|
||||
|
||||
Separately compared all eight archived raw/proof records for each of the five runs against the original `.local/scenario-group-gates` files: **40 exact byte matches**. Independently recounted the packaged focused stdout names: **12 Scenario + 8 Process successful tests**. Thus the tested names/counts reviewed earlier are the ones actually packaged, not merely those in a separate local log.
|
||||
|
||||
The retained selfcheck transcript reports original raw/optional-proof/helper equality, existing-output refusal and exactly 11 named corruption rejections. I read this transcript; I did not rerun the preparation selfcheck, since it creates temporary package outputs. `checks/selfcheck.py` remains explicitly outside the pinned offline inventory, consistent with the report; the official verifier does not require it. All relative links in the updated delivery record now resolve, including checks/README and the selfcheck log.
|
||||
|
||||
One initial reviewer attempt used LLVM-MinGW's bundled Python 3.14, which lacks zlib and failed before reading compressed members. Retrying with the full available runtime passed. This is a verifier-runtime limitation, not a package finding; no install or environment modification was needed.
|
||||
|
||||
**Final bounded assessment:** no material findings. Closed gate claims and the assembled local evidence package are internally consistent and accurately bounded to the planner foundation. Suitable for the parent's delivery/publication step after its ordinary coordination; this review does not claim that remote publication has happened, does not accept any Scenario group UI/gesture implementation, and does not modify the independent prerelease scope.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Scenario group planner: independent source review
|
||||
|
||||
Reviewed 2026-09-24 for the parent session's issue 29 contribution. Scope: staged port of preserved WIP `763f39d` onto combined base `897d9f049406d5be597543eb0a6755b796259302`, working branch identified by parent as `codex/scenario-multiselect-9e8r6t-20260924`. HEAD was independently checked at that base. This is local-only post-prerelease work; it does not revise or accept the independently frozen issue 37 release.
|
||||
|
||||
Read AGENTS.md, PROJECT_GOAL.md, staged changes, the accepted policy and prior independent policy review, relevant core execution/validation/layout code, Document execution/history, and the existing singleton App call path. No Cargo, GUI, source/test changes, Git mutation, Gitea operations, or remote publication were performed. This report is the only file written. The seven staged planner tests were inspected, not executed; parent is independently extending the suite.
|
||||
|
||||
## Result
|
||||
|
||||
No material correctness defect or combined-schema incompatibility found in the reviewed pure planner. This is a bounded source-review result, not implementation, native workflow, integration, or release acceptance. No change to the strict same-parent/no-dependency-expansion policy is needed.
|
||||
|
||||
## Evidence
|
||||
|
||||
- `crates/oa-desktop/src/behavior/scenario/group_reorder.rs:20` admits actual Exchange, Action, State, ScenarioUse and Fragment bodies, rather than generic Occurrence kind. `:60` examines every target and rejects foreign variants, duplicate IDs, missing elements and ineligible bodies without filtering the submitted set. `:67` establishes one exact immediate parent; an empty set refuses at `:73`.
|
||||
- `group_reorder.rs:32` follows only listed Fragment steps and their listed, correctly parented Branch bodies, with a visited set. Therefore an omitted operand or an unlisted containing Fragment cannot manufacture reachability through ancestry. `:77` then checks the entire edited sequence for unique, existing, eligible, correctly parented entries; selected membership is checked at `:85`. It intentionally does not require every parented child to be listed, preserving the supported warning-only draft state.
|
||||
- `group_reorder.rs:90` validates the immutable full baseline before a Prepared value can exist. `:95` checks destination parent and `:99` anchor identity before selected-anchor Noop at `:102`. A selected anchor retains the original noncontiguous list, rather than compacting the block. Both block and remainder at `:104-106` are filtered from original semantic order; BTreeSet iteration/click order never determines order. This implements `[A,B,C,D]`, selection C/A, before D => `[B,A,C,D]`.
|
||||
- `group_reorder.rs:109` executes the full candidate with `execute_batch`; `crates/oa-core/src/session.rs:292` gates only Error diagnostics, and `:304` clones/applies/validates the entire model. Thus descendant replies and lifetime uses participate through the existing validator, while missing-reference and loop warnings remain advisory. Relevant nested semantics are `validation.rs:1493` (guaranteed order), `:1520` (possible order and ALT/PAR distinction), `:1600` (local lifeline uses including opaque ScenarioUse bindings), and `:1781` (Create/Delete lifetime checks).
|
||||
- `session.rs:739` replaces only the addressed steps list. The planner emits no other command. `references.rs:448` performs no deletion cleanup without pending deletions; `data.rs:378` returns without pruning when inheritance is unchanged. This supports preservation of branches, descendants, bindings, correlations, IDs, relations and other records.
|
||||
- New combined saved Scenario layout data is compatible with this operation: `crates/oa-core/src/scenario_layout.rs:102` computes structurally reachable message membership, and `:154` validates identity/ownership/address/finite-coordinate constraints rather than imposing saved-Y ordering. Same-parent permutation preserves that membership. The planner does not rewrite authored lanes/message positions or unrelated views. A rendered-layout usability check remains separate from structural validity.
|
||||
- The singleton wrapper at `crates/oa-desktop/src/behavior/scenario.rs:1251` retains its existing Command-returning interface, forwarding a real change or returning an identical SetSteps for Noop. Its same-list result matches old singleton reorder semantics; the tighter type/reachability/baseline checks are deliberate policy enforcement. Source preserves camera, focus, spacing and presentation imports/modules from the combined base.
|
||||
- Tests `group_reorder/tests.rs:27-49` use a literal expected parent list applied to a cloned complete Project, then compare full candidate/Document state, source format/parse, bundle reopen, and one Undo/Redo. This is stronger than checking only selected positions. The source review confirms the test design, not their result.
|
||||
|
||||
## Remaining evidence and adapter boundaries
|
||||
|
||||
1. Parent's already planned discriminating conditional Delete, ALT-vs-PAR, warning and saved-layout fixtures remain necessary before planner acceptance. Existing positive tests contain no authored saved layout records, so full-Project equality alone does not exercise preservation of that new data. Include nonempty layouts on affected and unrelated saved Scenario views, plus metadata/value/reference records, then require exact preservation outside the chosen parent list.
|
||||
2. Add a bounded singleton no-op regression while touching its wrapper: with an existing redo branch, obtain the identical SetSteps via `scenario::reorder`, execute it through Document, and verify exact bundle/revision/history. The existing no-op test at `group_reorder/tests.rs:71-91` intentionally calls only `Prepared::plan`; it cannot establish behavior of the wrapper's actual Command execution. `document.rs:322` recompiles and `:399` suppresses equal-bundle commits, so source suggests preservation, but it is not exercised by that test. This is an evidence gap, not a demonstrated regression.
|
||||
3. The pure planner has no document revision, surface, exact rendered visibility or selection-generation identity. Its owned Arc snapshot correctly freezes semantics, but a Change command must not be executed against a changed live model. The forthcoming App adapter must perform the policy's full snapshot/membership refusal before reconciliation or execution, and preserve primary/selection on refusal/Noop. Do not infer those guarantees from the current source-only planner tests.
|
||||
4. Preserve a genuine Noop path in the forthcoming group adapter. Existing singleton `app.rs:2514` executes a Command and unconditionally reports a reorder on success, including an unchanged Command; this predates the port and is not a new planner defect. The adapter should provide explicit no-change/refusal feedback and must not turn selected-anchor Noop into a new history transaction or claim an undoable move.
|
||||
5. Build and execute the port plus existing Scenario direct/workspace regression tests against the identified combined source. Native acceptance must cover nearest-slot parent refusal, selected Fragment interiors, camera/spacing interaction, saved-layout rendering, stable whole-set preview/commit, cancellation and exact one-step history. No group UI adapter exists in the reviewed diff; no aggregate removal acceptance is implied.
|
||||
|
||||
No production fix is requested from this review. Follow up on the bounded evidence above and separately review the adapter once present; do not broaden to cross-parent movement, selected-subset pruning, parent/child normalization or automatic dependency closure.
|
||||
|
||||
|
||||
## Follow-up: expanded staged evaluator readback
|
||||
|
||||
The parent requested a second read after formatting and extending the suite to 12 test families, before the first focused execution gate. I reread the new tests and policy/contract changes. No Cargo/GUI or code/test edits were performed. The earlier line numbers identify the original unformatted port; the locations below identify this formatted readback.
|
||||
|
||||
No concrete fixture/oracle defect found. Proceed to the focused gate; passing execution remains required and is not asserted here.
|
||||
|
||||
- `group_reorder/tests.rs:132`: the literal root/operand stable-block oracle now runs in both OA and SA. `behavior/topology.rs:572` creates each occurrence in its actual parent's layer, so the SA cases are genuinely SA trees rather than OA bodies hidden under an SA owner.
|
||||
- `group_reorder/tests.rs:155`: both selected-anchor and unchanged-unselected-anchor singleton cases obtain a Command from the modified wrapper and execute it through Document after Undo. Exact project, bundle, revision, empty past and preserved redo checks now cover the earlier singleton evidence gap. Nested operands are also covered.
|
||||
- `group_reorder/tests.rs:489`: conditional Delete fixture keeps the original Call/Reply before the later ALT fragment, then validates the complete baseline. Moving the earlier Action/X block to append is therefore the operation that introduces the post-delete use; the asserted `lifeline_lifetime` error is not merely inherited from an invalid starting model.
|
||||
- `group_reorder/tests.rs:516`: the ALT fixture has separate actual operand lists for Delete and Use/X/Y. The literal expected right list `[Y,Use,X]` follows semantic block order although selection is X/Use. The PAR control correctly asserts baseline `lifeline_lifetime` and preparation refusal, without claiming a valid reorder caused the PAR error.
|
||||
- `group_reorder/tests.rs:558`: Reply is removed from the root list, reparented into the Loop operand and listed there exactly once. The outer Call still precedes its containing fragment. The test checks that `reply_repetition` is attached to that Reply with Warning severity in both original and literal expected models. `[Reply,X,Y,Z]`, selected Z/X, append => `[Reply,Y,X,Z]` is the correct stable-order oracle. Preparation and full transaction independently require no Error diagnostics.
|
||||
- `group_reorder/tests.rs:599`: two saved views of the affected Scenario carry distinct, nonempty lane and Call/Reply position records plus metadata. A third saved view belongs to a separate Scenario and contains an actual local Timer with sender lifeline and absent receiver, which matches the existing timer contract. All are admitted before planning. The only intended difference in the expected complete Project is the affected root list `[X,Y,Call,Reply]`; exact_transaction additionally checks format/parse, bundle reopen, and exact Undo/Redo. This now exercises the combined saved-layout preservation gap rather than relying on empty/default records.
|
||||
- Policy explicitly distinguishes semantic ordering from authored Scenario presentation, preserves stored coordinates/metadata/views, and requires representation plus visit-lifetime admission, including a same-owner round trip. The contract mirrors that distinction. These changes resolve the stale assumption that every Scenario presentation is wholly derived. The cross-commit Allocation evidence link is explicitly scoped to its published source rather than implying that report exists in this combined tree; I did not independently retrieve/verify the remote publication in this bounded review.
|
||||
|
||||
The new source tests address the original report's items 1 and 2 at the evaluator-design level. Their runtime outcome is still pending. Items 3-5 remain adapter/integration/native boundaries. No production repair or extra scope is requested from this follow-up.
|
||||
@@ -0,0 +1,24 @@
|
||||
planner-focused-1 recorded exit 101 inputs 804 executable records 0
|
||||
planner-focused-2 recorded exit 0 inputs 804 executable records 1
|
||||
scenario-regressions-1 recorded exit 0 inputs 804 executable records 1
|
||||
format-1 recorded exit 0 inputs 804 executable records 0
|
||||
strict-lint-1 recorded exit 0 inputs 804 executable records 0
|
||||
planner-focused-1 recorded exit 101 inputs 804 executable records 0
|
||||
planner-focused-2 recorded exit 0 inputs 804 executable records 1
|
||||
scenario-regressions-1 recorded exit 0 inputs 804 executable records 1
|
||||
format-1 recorded exit 0 inputs 804 executable records 0
|
||||
strict-lint-1 recorded exit 0 inputs 804 executable records 0
|
||||
PASS package readback: C:\ar-scenario-9e8r6t\.local\scenario-evidence-tools\selfcheck-1 {'runs': 5, 'recordedFailures': 1, 'logicalFiles': 43, 'storedMembers': 37, 'sourceCapsulePairs': 2, 'captureHelpers': {'gate.py': 'helpers/3e4cd38fd3807a4010a5a7102354f9139cab236ca55163c96090e416161e5fd2.py', 'source-proof.py': 'helpers/c86f51d9b8465341dab73d045923ec306b15ebabf7c97b81beac7afe73bd72a1.py'}, 'sourceProofs': 5}
|
||||
planner-focused-1 recorded exit 101 inputs 804 executable records 0
|
||||
planner-focused-2 recorded exit 0 inputs 804 executable records 1
|
||||
scenario-regressions-1 recorded exit 0 inputs 804 executable records 1
|
||||
format-1 recorded exit 0 inputs 804 executable records 0
|
||||
strict-lint-1 recorded exit 0 inputs 804 executable records 0
|
||||
planner-focused-1 recorded exit 101 inputs 804 executable records 0
|
||||
planner-focused-2 recorded exit 0 inputs 804 executable records 1
|
||||
scenario-regressions-1 recorded exit 0 inputs 804 executable records 1
|
||||
format-1 recorded exit 0 inputs 804 executable records 0
|
||||
strict-lint-1 recorded exit 0 inputs 804 executable records 0
|
||||
PASS original raw/optional-proof/helper archive equality, existing-output refusal, and corruption rejection: ['raw stream/receipt mismatch', 'helper bytes/name mismatch', 'run/result exit mismatch', 'input archive mapping mismatch', 'missing closed receipt', 'source proof run relationship', 'source proof commit relationship', 'source proof result relationship', 'source proof inputCount relationship', 'missing declared optional source proof', 'source comparator bytes mismatch']
|
||||
Historical captured failures remain failures: 1
|
||||
No Cargo, archived code, or GUI executed.
|
||||
@@ -4,7 +4,7 @@ Derived from [issue 29](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Stud
|
||||
|
||||
## Operation and identity
|
||||
|
||||
A Scenario drag changes semantic sequence order. It moves the exact selected local occurrences as one stable block inside their common immediate Scenario or Branch parent. It does not edit free XY positions, migrate operands, change a referenced definition or automatically select dependencies.
|
||||
This semantic reorder operation moves the exact selected local occurrences as one stable block inside their common immediate Scenario or Branch parent. It does not edit free XY positions, migrate operands, change a referenced definition or automatically select dependencies. Saved-view body drags already adjust presentation spacing; this policy does not redefine their default meaning.
|
||||
|
||||
Saved Scenario layouts are separate presentation records. Semantic reorder preserves all authored lane/message coordinates, metadata and other views exactly, even when the changed order requires derived presentation to accommodate those preferences. A future group presentation move is a separate capability; this increment does not silently reinterpret stored coordinates as sequence order. App admission must distinguish the active saved/derived representation and its visit lifetime, including a same-owner representation round trip.
|
||||
|
||||
@@ -41,3 +41,13 @@ Contiguous-only selection rejects useful sibling groups without simplifying nest
|
||||
Discriminating evaluators include literal whole-Project transformations at root and operand levels, noncontiguous order versus click order, selected-anchor/unchanged no-op with existing redo, malformed unselected entries, omitted Branch reachability, warning-only unlisted children, nested reply/lifetime failures and legal nested-fragment movement. Successful cases must preserve every other sequence, binding, relation, value, ID and metadata record. Use independent source round trips and one-step Document history, not preview/commit agreement alone.
|
||||
|
||||
Aggregate removal remains separate required work. Until it exists, an interface displaying a group must refuse legacy primary-only Remove/Delete/reorder actions. Live ownership and progress remain in issue 29.
|
||||
|
||||
## Adapter decision boundary after combined-source review
|
||||
|
||||
Use the existing shared Selection/Snapshot with a full Scenario Context envelope. Map a derived representation to `Surface::Scenario(owner)` and a saved representation to `Surface::Saved(diagram)`. Representation navigation clears the group; the envelope additionally admits document, owner, representation, visit, revision and epoch, including A-to-B-to-A visits. Exact visibility comes from the actual presented scene. This choice preserves existing selection semantics without adding Scenario-specific fields to every generic adapter.
|
||||
|
||||
The saved-view gesture is still an open design decision. The initial [adapter mapping](scenario-group-planner-evidence-20260924/reviews/adapter-design.md) proposed a one-shot arm; the [independent critique](scenario-group-planner-evidence-20260924/reviews/adapter-critique.md) identified a simpler group handle and an operation-local order preview worth comparing first. Authored message coordinates may remain visually unchanged after a real semantic reorder, so an arm label alone does not establish observable success. Compare a handle plus order preview with an arm plus order preview using the same noncontiguous A/C-before-D task, then an ordinary spacing edit. Preserve the active saved representation and captured group during the operation preview; do not transfer selection across actual representation navigation.
|
||||
|
||||
Before enabling group input, specify a consumed gesture identity (or prove equivalent epoch retirement) so canceled, completed/no-op and duplicate packets cannot affect a newer gesture on the same unchanged selection. Match captured selection identity on spacing packets too; group-size checks alone miss selection ABA. If returning to the original press position promises no change, define that screen-space rule separately from nearest-slot selection; the nearest slot need not be the pressed occurrence's anchor. Close raw Details arrows, menus and singleton removal/reorder paths before they can apply to only the primary of a visible group.
|
||||
|
||||
The planner's [verification record](scenario-group-planner-20260924.md) establishes the bounded semantic foundation. It does not accept any of these future UI choices or claim group gestures are implemented.
|
||||
|
||||
Reference in New Issue
Block a user