23 KiB
Source CLI: independent contract review before implementation
Codex-9e8r6t /root/behavior_audit, 2026-09-11. Read-only review of the existing core and the proposed CLI contract; no production edits, Cargo, direct rustc, native input, or CLI test execution. /root/palette_finish owns crates/oa-cli. Root owns integration and release acceptance.
Reviewed AGENTS.md, issue #1 through #143, the empty open-PR list, Wiki Authoring20260911, and the imported release-textual-authoring-design-20260911.md. Inspected core is the working tree at HEAD 4bb4aaf3dc47b9738625b4d3f23ef8c74f286395; this review did not inspect the author's evolving CLI implementation. References below are relative to the repository root. Existing tests are evidence of intended contracts, not newly executed results.
The proposed slice is coherent: explicit entry file; check/lint against original bytes; stabilize and semantic rename-alias preview by default; --write uses the same prepared candidate and the loader's original disk expectations. It does not select a new grammar, namespace scheme, identity policy, or source7 constructor defaults. The external design identifies this as useful immediate infrastructure, not completion of the text-only usability objective.
Material traps
1. P1 — Loader diagnostics do not uniformly describe the raw files it returns
source_files::discover_missing retains original file text and bytes, then calls compile_bundle to populate DiskProject.diagnostics when discovery has no earlier diagnostic (crates/oa-core/src/source_files.rs:528). Compilation inserts missing UUIDs and reindexes the returned text (source_project.rs:286). Consequently, a successful load can return raw files with warning spans from stabilized files. Appending those warnings to raw lint output duplicates warnings and can point an editor beyond the intended declaration.
The existing counterexample is two missing-ID functions in one included file: a warning for the second function moves when the first UUID is inserted. crates/oa-core/tests/source_project.rs:207 explicitly distinguishes these positions. Use lint_bundle(snapshot.reachable_bundle(), limits) as the semantic diagnostic authority for original bytes. Preserve loader-only discovery, encoding, path, and pending-save failures; replacing every loader diagnostic with lint would turn invalid UTF-8 into a less useful missing-file complaint or lose the journal refusal. Do not blindly reuse the loader's compiled semantic warnings.
There is a second identity dimension: lint_bundle calls compile_once, and Parser::optional_id allocates fresh UUIDs for omitted clauses (language.rs:1226). A raw lint warning's object_id can therefore differ from the later compiled candidate's UUID even though its raw position is correct. JSON must either omit such provisional identities, identify their provenance explicitly, or deliberately map them to the candidate. It must not imply that an independently allocated lint UUID identifies the record in the diff. Explicitly authored IDs remain stable.
The same separation applies to a failure during stabilization or migration after raw lint succeeds, such as the inserted IDs exceeding the source-byte budget. Its candidate-location diagnostic must not silently be presented as an original-buffer span. Spans are half-open UTF-8 byte offsets; line/column are one-based, with column counting Unicode scalar values (language.rs:107, source_project.rs:640).
Status: sent directly to the CLI author and root before implementation.
2. P1 — Keep the captured write baseline, including for no-op writes and failures
The correct write authority is the original DiskProject.expectations, which records bytes before stabilization (source_files.rs:43). Do not reload after preparation or replace those expectations with candidate bytes. Either would discard evidence of an intervening editor change. Check every captured dependency, including a file the proposed rename does not change.
An empty diff is not permission to skip save_project: the core checks the pending journal and all original expectations before its no-write return (source_files.rs:880–902). A prepared no-op followed by an external edit must refuse, rather than report an up-to-date successful write. A successful unchanged write should perform no file replacement and create no journal.
The core promises recoverability and checks for observed external changes, not a cross-process CAS, multi-file atomic visibility, or Windows power-loss durability (source_files.rs:1–18). A noncooperating writer can change a file between a check and its replacement. Different entry files can also have overlapping dependencies without sharing one journal. Neither this CLI nor its diff adds a stronger guarantee. No extra locking design is required for this bounded slice; describe the actual guarantee accurately.
Distinguish invalid-input refusal before saving from a disk failure during saving. save_project can have written files, attempted rollback, or left a pending journal when recovery cannot finish. Exit 3 must retain the specific conflict/recovery outcome and must not always claim that nothing was written. The saver may recover its own failed transaction internally; the CLI must not automatically recover a pre-existing transaction.
Status: author already proposed original expectations and no CAS claim; the empty-diff bypass and failed-write wording are explicit acceptance boundaries.
3. P2 — Preserve migration evidence before the rename updater removes it
compile_bundle can perform a targeted upgrade of accepted source3–6 in addition to UUID stabilization. The migration can add semantic defaults and generated interface allocations, not merely change a version token (source_project/source_migration.rs:4–105, persistence.rs:15). Capture this initial decoded.migration before applying SetAlias.
update_bundle_with_identity_map recompiles the already-upgraded bundle at source_project.rs:1016. The final schema7 result has no new migration report. Reading only final_candidate.decoded.migration would therefore hide the fact that a rename also upgraded the project. Preserve the initial report separately and show the complete original-to-final file diff, including generated declarations and defaults. The same-alias command may still change source through stabilization or migration; changed must compare original and final bytes, not only the semantic rename result.
The bundle parser explicitly admits source3–7 (language.rs:1404). Source1/2 migration belongs to the separate single-string legacy API; tests/source_project.rs:605 explicitly proves those inputs are rejected by compile_bundle. Do not silently fall back to language::parse plus canonical export: that changes format, ownership, trivia, and the advertised command. State the supported input versions and retain unsupported/invalid raw files unchanged.
Source7 omitted defaults keep their existing meanings, including Scenario viewpoint MixedLegacy. CLI stabilization must not adopt a more helpful inferred viewpoint, allocation, parent, or occurrence order. Existing constructor-default probes and the external design are consistent on this boundary.
Status: sent directly to author/root; the author already intends explicit migration presentation.
4. P2 — A missing-ID preview is a proposal, not a persisted plan for a later process
Compiling the original missing-ID files twice allocates different UUIDs; the external design's executed probe already establishes this. Prepare the candidate once per invocation, and pass that exact model/bundle/index to saving. Do not regenerate a rename from raw files after displaying its diff or select the target by an ID allocated during the separate raw lint pass.
Resolve OLD by its exact stored alias in the compiled project's full identity namespace, then use prepare_command(Command::SetAlias(...)) and its identity_map with the source updater. That namespace includes project, elements, relations, diagrams, placements, artifacts and traces; display names are not unique identity selectors (model.rs:1727, session.rs:170,607,816).
Separate preview and --write invocations may produce different uncommitted UUIDs. The author proposes disclosing this and deferring persisted plans, which is adequate for this slice. Do not describe --write as applying the previous invocation's exact patch. Once stabilized source has been written, repeated preparation must preserve all IDs. Never make name/path/content-derived IDs to conceal the preview difference.
For already stable source7, alias rename must change only the chosen stored alias and live reference tokens, preserving UUIDs, display names, comments, quoted prose, historical UUID literals, source membership and all other fields. Alias-like text inside a description is not a reference. The core updater already has the appropriate token behavior (tests/source_project.rs:877); do not implement a parallel text replacement algorithm.
Status: author explicitly accepted the per-invocation limitation and semantic updater path.
5. P2 — Journal and warning policy must survive the loader-to-CLI boundary
The existing loader appends a located pending_save diagnostic from the actual private journal path (source_files.rs:534–547). Use that diagnostic rather than recreating the journal naming convention. Loading itself is read-only but may compile internally before detecting the journal, so the accurate promise is refusal before the CLI prepares/adopts/writes a candidate, not detection before any load/compile work. The saver independently checks again. A pending journal remains a refusal even when source has no proposed change or contains a syntax error. Reserved stage/completed directories alone are not pending transactions.
The proposed policy—warnings exit 0, invalid source exit 1, usage exit 2, disk/conflict/journal refusal exit 3—matches core admission for structural errors versus engineering warnings (session.rs:276). Missing allocation, an empty scenario, or another allowed partial model must not become unsaveable merely because warnings exist. Conversely, raw invalid UTF-8/syntax, unresolved required references, unsupported versions, and path violations must never be replaced by a canonicalized or partially accepted bundle. Preserve their bytes and any pending journal. If an IO failure is represented as a discovery diagnostic, classify it consistently instead of assuming every diagnostic means exit 1.
lint_bundle may stop at the first syntax/conversion error, and structural-error admission returns errors rather than all engineering warnings (source_project.rs:580–612). A JSON result is not an exhaustive list of every defect; the CLI must not claim otherwise. Refactoring argument errors (absent source alias, invalid/reserved/colliding destination alias) likewise require a clear nonzero refusal with zero writes; distinguish command diagnostics from real source positions.
Compact independent acceptance matrix
These are proposed CLI contract tests, not newly executed results. Compare complete Project and SourceBundle where a candidate exists, plus exact disk bytes for refusal/no-op cases.
| Case | Exact expected result |
|---|---|
| Raw diagnostics | Include a file with two missing-ID functions, CRLF and Unicode before a warning. Check/lint human and JSON locations select the original declaration; no stabilized warning duplicate or false candidate UUID. Explicit-ID version retains its real object identity. |
| Invalid discovery and pending save | Invalid UTF-8 include, missing include, syntax failure, and real captured pending_save separately retain disk bytes and useful loader diagnostics. Pending save also refuses an otherwise no-op write; unrelated stage/completed directories do not imply pending recovery. A journal appearing after preparation is refused by saving. |
| Missing-ID rename | Resolve an alias across includes, prepare once, and save that exact candidate. Readback is byte-for-byte candidate source and equal model with its exact IDs. A later fresh process can resolve the new alias. Two unwritten preparations may have different fresh IDs; the documented preview limitation is visible. |
| Stable alias rename and rejection | Existing UUIDs, duplicate display names, comment/prose containing the old alias, an @ reference separated by a comment, and a non-element alias target. Only intended alias/live reference tokens change. Unknown OLD, invalid/reserved/colliding NEW refuse without writing. |
| Exact no-op and stale dependencies | Stable source7 stabilization and same-alias rename produce an empty diff and preserve exact bytes/line endings. Prepare the same no-op, edit or delete an unchanged dependency, then call the save boundary: exit 3 with no overwrite of the external bytes. |
| Migration and admission | A valid old bundle with all UUIDs explicit still shows its migration and full diff for a real or same-alias rename. The report survives the updater. Source1/2 and invalid old-source drafts stay untouched. A candidate exceeding its budget after insertion is refused without a false raw location or any write. |
| Warning-only acceptance | A permitted unallocated function or empty explicit-viewpoint scenario reports warnings and exits 0 for check/preview/write; acceptance does not invent allocations, change viewpoint, or populate steps. Structural errors exit 1 and preserve exact input. |
| Detached disk files | A no-longer-reachable .arcadia file from a prior Undo remains byte-identical. The explicit entry's reachable source alone defines the model; do not recursively add or delete unrelated files. Ordinary exact no-op compares the reachable bundle while separately proving the retained disk file unchanged. |
No implementation blocker requires a core redesign. The actionable work is to preserve the distinct raw-diagnostic, compiled-candidate, original-disk-expectation, and migration-report contracts in the adapter. Root should request an independent implementation review after the CLI source freezes; this report is not CLI or release acceptance.
Frozen implementation review and independent process evidence
Codex-9e8r6t /root/behavior_audit, 2026-09-11. This section supersedes only the earlier unreviewed implementation status. Reviewed the final crates/oa-cli/src/lib.rs, src/main.rs, all 12 author tests and their terminal logs, the relevant unchanged core parser/updater/saver paths, issue #1 through #170, Wiki Authoring20260911, and the empty open-PR list. Read the user's updated remote AGENTS.md blob d93cddacdd45341abb7bbea1d3e3fd4493484d9e, including coordinated milestone prereleases; root retains release ownership. No production edits, Cargo, rustc compilation, native input, commit, or push was performed for this review.
Result: no remaining blocker found in this bounded CLI slice. One presentation claim was corrected during review. The actual write and diagnostic boundaries preserve the prepared contract; broader text authoring and release acceptance remain separate.
Corrected finding: P3 — describe the changed files, not a whole-candidate identity
The first candidate note said that the invocation's candidate was shown in full, while Report.changes contains only changed files. Two projects with different unchanged include bytes can therefore yield identical changed-file output. This is expected for a diff report, but the note overstated what it identified. The author corrected lib.rs:353 to describe complete proposed changed-file texts/diffs and explicitly disclaim a saved plan or unchanged-dependency digest. Root confirmed that no persisted-plan or digest protocol was selected; this review does not require one.
Unchanged dependencies are still protected when saving. Prepared keeps the original DiskProject and compiled candidate in private fields; Prepared::write always calls source_files::save_project (lib.rs:179–205). It has no empty-diff bypass and does not reload or substitute expectations. The core checks all captured expectations (source_files.rs:799–816,884) and the actual journal before its no-write return at 902. Thus an include absent from changes is not absent from the write precondition. The author separately exercised a changed unchanged include and a captured no-op changed after preparation; those are author test results, not independent runtime claims from this review. No cross-process CAS guarantee is added.
Other contracts traced to their actual implementation
- Raw diagnostics and identities: loader-only discovery/encoding/path/journal diagnostics survive; raw
lint_bundlesupplies semantic spans; stabilized loader warnings are filtered (lib.rs:224–270). Successful raw warnings retain object IDs only when those IDs were authored in the input. Invalid raw text returns before declaration analysis/updating, and later candidate failures explicitly saycandidate. Independent process probes below exercise the shifted-second-declaration and non-BMP-column counterexamples. - Semantic alias coverage and source preservation: target resolution uses the compiled project's complete identity namespace, then
prepare_command(SetAlias)andupdate_bundle_with_identity_map(lib.rs:307–338). It never uses an independently allocated lint UUID as its target. The core covers project, element, relation, diagram, placement, artifact and trace identities. Independent exact-source probes cover all seven, with comments and quoted old aliases deliberately retained. - Migration/default boundary: the adapter captures the initial migration report before the semantic updater (
lib.rs:305), compares original raw text with the final candidate, and has no canonical-export, legacy-parser, inferred-viewpoint or implicit-allocation fallback. Bundle admission remains source3–7; source1/2 are not silently converted. The author's source5 rename test verifies migration retention and original-to-final diff. This review inspected the other admitted-version branches but did not independently execute a full source3–7 constructor matrix. - Refusal and output:
--writeconsumes the same in-process candidate. Invalid source/commands use exit 1, usage 2, and path/conflict/journal/output failures 3; engineering warnings remain exit 0. A detected pending save is authoritative even when the source is invalid or the proposed write is unchanged. The CLI does not callrecover_project. Human mode flushes its preview before saving; JSON returns the operation's final report. Failure wording correctly avoids equatingwritten=falsewith guaranteed rollback. Separate missing-ID invocations remain distinct uncommitted proposals, explicitly disclosed.
Independently executed frozen-binary probes
Ran the author's immutable Windows executable .local/source-cli-20260911/verified/arcadia.exe, 20,312,064 bytes, SHA-256 BAD169F8BE78DCFA74B32714569C886DD2132727080684FC2BA618D5822E3559, with its supplied libunwind.dll. The executable hash was checked before and after the run. No compiler was used.
Command:
& harness/experiments/source-cli-independent-20260911/probe.ps1 -Repository 'C:\Users\User\Documents\Git\ARCADIA-Studio'
183 assertions passed across 21 fresh CLI invocations. The script computes every expected alias edit before the first mutation and writes only isolated .local/source-cli-independent-20260911/verified/ fixture files. It refuses to overwrite a prior run directory; a new -RunName allows a separate repeat. Evidence lives in harness/experiments/source-cli-independent-20260911/: probe.ps1, alias-input.arcadia, alias-expected-final.arcadia, and result.json with individual assertions, commands, outcome counts and hashes.
| Independent case | Observed result |
|---|---|
| All seven identity families | Project alarm_response, element maintenance, relation alarm_message, diagram response_view, placement detector_card, artifact response_procedure, and trace assessment_refinement each previewed and wrote exactly the precomputed declaration/live-reference token changes. All 40 existing UUIDs, display names, other fields, comments, quoted aliases and source ownership remained byte-exact under the independent expectation. |
| Preview, repeat invocation and stable no-op | Every preview left its input unchanged; each separate write of already-stable input matched its predicted preview exactly. Fresh final check succeeded. Stabilization then returned unchanged, no diff and written=false, preserving bytes and modification time and leaving no unexpected physical file. |
| Raw warning position versus stabilized candidate | Two missing-ID SA functions in one CRLF include with Cyrillic, CJK and non-BMP text produced exactly two raw unallocated_leaf warnings, without loader-warning duplicates or provisional IDs. The second candidate declaration genuinely shifted after UUID insertion; its reported warning remained at the original input byte offset. Explicit-ID variants retained the exact authored warning IDs. |
| Invalid Unicode reference | A non-BMP symbol before an unresolved reference distinguished UTF-8 byte offsets, Unicode scalar columns and UTF-16 units. The write attempt reported the exact original reference position, exit 1, no changes, no ephemeral identity, and preserved the complete input bundle bytes. |
The first harness launch stopped before starting the CLI because PowerShell's built-in cli alias resolved to Clear-Item instead of the intended helper. With stop-on-error, the missing literal check path stopped that invocation before the isolated source argument was handled. The helper was renamed to Invoke-ArcadiaProbe; the final clean run above passed. This was a harness error, not a product failure. The initial isolated input directory is retained; no production or original example file was passed to that mistaken command.
The author's separate gate is 12/12 tests passed, 1.88 seconds execution, plus strict all-target Clippy, formatting and matching build. I read their preserved initial 11/12 log: the failed fixture used unsupported block-comment syntax, then was corrected to supported line-comment syntax without a product change. These compiled gates remain attributed to /root/palette_finish.
This independent run proves exact source-byte predictions and process/report behavior for the stated cases. It does not claim an independent full-Project oracle, adversarial simultaneous-writer proof, exhaustive source-version/default coverage, Linux execution, native GUI usability, packaging or release acceptance. Those limits do not create an additional blocker for this bounded source CLI adapter.
Reviewed source fingerprints:
| File | SHA-256 |
|---|---|
crates/oa-cli/src/lib.rs |
97BEBAB63B4A611A1D36AD0C20FE452CBE1985FE3A6FBD39C954603DA11410FC |
crates/oa-cli/src/main.rs |
DE964F9B6C59C0F31C080AFBD3B31D442B07B1B44F6A187A248D3409C45D35FB |
crates/oa-cli/tests/source_cli.rs |
A3A1B856C7A9FD3D80BC8074150A24FBB0EF0E9DDF736C7CEA036725FC3DC2B8 |
crates/oa-core/src/source_files.rs |
669491DBDDB8E8E6556161426DFFA1A49AC1C826D31020BFF0956D81A4D84683 |
crates/oa-core/src/source_project.rs |
1394370BCC5908873B830C2C11312E6E1611AF41F46C4F43CA0A6BCB713BB466 |