Define bounded mixed source CLI validation contract

This commit is contained in:
2026-09-24 21:47:40 -07:00
parent d4d50f061b
commit f810fb40eb
3 changed files with 333 additions and 0 deletions
@@ -0,0 +1,164 @@
# Mixed CLI read-only check: implementation map
Design only against public-read branch bab6f96a (parent reports accepted gates).
No source edits, compiler or remote actions. Paths/lines refer to this source.
## Recommended user outcome
Add explicit `arcadia check-mixed ROOT [--json]`, requiring the public checker's
schema8 and explicit UUIDs. Preserve check/lint/stabilize/rename-alias behavior.
Use the existing report envelope with operation check-mixed, status checked or
invalid, no changes, written=false, migration=null and identities_to_stabilize=0.
The operation validates captured original bytes; it does not stabilize, export,
format, index or save. Help must state this distinction and reject --write.
The command choice and discovery architecture are separate decisions. Merely
adding a command enum variant does not make the existing loader support typed
syntax. A real parser-consistent discovery seam is the prerequisite below.
## Current blockers and reusable code
- oa-cli/src/lib.rs:213 prepare loads source_files::load_project, then ordinary
lint/analyze/compile (234–310). Operation::Check still returns Prepared holding
CompiledSourceProject and a disk snapshot; it is not a read-only mixed route.
- main.rs:76 dispatches all accepted arguments through prepare and optional write.
Add a separate report-returning mixed-check branch before that branch. Do not
forge Prepared or an empty SourceIndex. An explicit Arguments command enum can
separate read-check from existing editable Operation, or use a distinct
check_mixed flag/variant handled before prepare; avoid illegal write combinations.
- source_files.rs:329 load_project creates a DiskProject then discover_missing.
Discovery's DFS reads bounded bytes and tracks originals/expectations (389–494),
but495 calls ordinary inspect_includes and529 runs ordinary compile_bundle.
- source_project.rs:207–268 inspect_includes validates paths and calls ordinary
language::parse_unit, so it rejects valid short typed declarations. Filtering
that diagnostic cannot reveal the missing include graph safely.
- mixed.rs:390–422 already lexes and invokes the shared Parser::unit with actual
mixed State and explicit IDs, producing ordinary.includes plus typed syntax.
This exact unit grammar can inspect includes without resolving global references.
Extract/reuse this narrow unit operation instead of a regex, guessed brace
scanner, second quote decoder or calling the complete checker on partial bundles.
- source_files.rs:104–216 root_entry/check_logical/disk_path/read_bounded already
enforce root containment, canonical logical names, reserved journal namespace,
link/regular-file restrictions and bounded reads.240 check_snapshot and293
check_physical_aliases preserve aggregate and physical-alias checks. Reuse these
functions in one core discovery engine; do not replicate their security logic in CLI.
## Minimal safe ownership shape
Introduce a named read-only discovery entry in source_files, with a small private
closed selector OrdinaryEditable / MixedRead for shared discovery dispatch.
Existing public discover_missing/load_project select OrdinaryEditable and retain
all current completion/lint behavior. MixedRead selects mixed include inspection
and skips ordinary compile/stabilization; it still performs all disk/path/journal
checks. No generic caller-provided parser/loader callbacks or new public framework.
Prefer an opaque owning `MixedSourceSnapshot` exposing only bundle() and
load_diagnostics(), or a narrowly scoped `load_mixed_source` result owning those
values. Do not advertise it as a save snapshot or provide conversion into editable
CompiledSourceProject. Internally reusing DiskProject's captured bytes is possible,
but returning DiskProject publicly would unnecessarily expose save/discovery
operations and expectations. Keep original raw-byte expectations private if reused.
This design adds a small wrapper, not a clone of every buffer for an extra model.
CLI owns the snapshot, borrows its complete reachable SourceBundle while calling
language::check_mixed_bundle, maps warnings/errors into the existing Report, then
drops CheckedMixed before dropping source. Report owns mapped diagnostic strings;
no borrowed checker escapes and no self-referential source/result struct is needed.
Report has no Project field: complete model evidence comes from independent core
and CLI-library tests of those same literals, not an invented JSON assertion.
## Discovery parser work and limits: resolve before implementing
Discovery is an extra syntax pass; final borrowed checker remains the sole complete
semantic admission. Its existing budgets must be fresh for that full pass; do not
subtract discovery work from final counts or represent a successful partial scan
as an admitted model. Re-reading/reparsing captured strings is explicit bounded work,
not a claim that the checker itself reparses or that disk is reread between phases.
The mixed unit helper needs a genuine Budget and ModelCounts. A naïve new budget
per file silently multiplies allowed discovery numeric/token/reference work by the
file count; a single shared budget with all retained-text totals left charged after
units are dropped misrepresents live storage. Therefore contract must choose a
small explicit **discovery-phase work policy** and lifetime:
- Recommended: one shared discovery work budget across all captured file parses,
cumulative bytes/tokens/references/numeric/syntax-event work, with per-unit syntax
owners dropped after copying only include locations. Reuse Parser::unit and its
resource checks. Name and check temporary per-unit metadata/text ownership before
growth if selecting producer mode; reconcile/release it on unit destruction.
This is a real small producer-lifetime seam to specify, not existing free behavior.
- Simpler alternative: per-unit bounded inspection with a documented aggregate
upper bound of max_files times its cap. It is finite but can be far larger than
the selected checker work profile; do not choose it silently or describe it as
inheriting one whole-project numeric budget. Parent may reject that cost policy.
- Do not retain all parsed witnesses then clone/reparse the complete bundle merely
to avoid accounting a dropped unit. That grows live state and complicates source
lifetimes for no consumer benefit. Do not stream-load into a borrowed BTreeMap
while previously parsed units retain references into it; ordinary Rust borrowing
disallows this without additional ownership infrastructure, which is unnecessary.
The implementation contract should pin the selected discovery bound/diagnostic
order and helper's drop/refund rules before source edits. This is the main remaining
bounded design question, not an argument for full editable-source adoption.
## Filesystem and diagnostic contract
Preserve existing root/file checks, UTF-8 rejection, aggregate byte/file/edge/depth
limits, case/physical alias refusals and pending-journal detection. Root depth0;
include paths resolved by existing resolve_include; count repeated authored edges.
Final complete check still rejects cycles/semantic failures using its exact original
coordinates. Discovery diagnostics prevent invoking checker with missing/invalid
buffers. Return exit3 for disk/path/pending-journal failures under existing CLI
mapping; exit1 for source/semantic rejection,0 for valid with advisory warnings,
2 for invalid arguments. Pin mixed loader's diagnostic precedence in tests rather
than assuming it equals in-memory checker's earlier/later limit branch.
Do not suppress pending_save simply because operation is read-only: existing CLI
refuses it, and the captured files may represent interrupted save state. No journal
recovery is implicit. Neither old nor new read path promises atomic multi-file
snapshot/CAS against concurrent external replacement (source_files.rs:177 comment).
Promise unchanged bytes by this command and validation of captured bytes, not
that the disk still equals that snapshot after a concurrent editor writes.
## Compared/rejected options
- Automatically replace ordinary check with mixed: breaks existing omitted-ID and
migration behavior. Schema8 alone does not distinguish ordinary drafts from the
stricter explicit-ID mixed contract. Avoid heuristic detection/fallback on errors.
- `check --mixed`: coherent alternative explicit spelling, but adds an option whose
invalid combinations must be excluded for mutations. Dedicated check-mixed makes
the limited outcome easier to explain; select one spelling, not both initially.
- Explicit all-files CLI manifest/bundle: can reuse checker immediately, but forces
users to enumerate includes and introduces another input/argument format. It is
useful only if a real pipeline already supplies bundles; not the recommended
ordinary filesystem workflow and not a substitute for safe include loading.
- Full compile_bundle adoption: requires typed stabilization/index/update/formatter
and application consumers; none is necessary for this read-only outcome.
## Finite acceptance and scoped files
Core changes: source_files shared discovery selector + read-only snapshot;
language mixed unit inspection helper and narrow reexport for core-only use;
source_project include-location conversion reuse where appropriate. CLI lib
arguments/report path + main dispatch, docs/source-cli.md/help, focused tests.
No changes to updater/save/formatter or public CheckedMixed authority.
Test actual executable on independently authored OA/SA three-file fixtures with
cross-file typed/scoped references and ordinary nesting. Exact complete JSON
warning/error reports, raw original byte hashes/directory listing unchanged after
each command, including CRLF/Unicode and comments mentioning include/aliases.
Core tests compare independently authored full models for those same bytes.
Exercise missing/cyclic include, invalid path/root escape, case/physical aliases,
nonregular/invalid-UTF8 file, pending journal and exact aggregate/depth/edge limits.
Only claim platform-specific link/FIFO tests where cfg permits; Linux remains
user-deferred. Exercise missing IDs in all four classes and typed unresolved/wrong
kind errors. Verify --write rejection and all existing ordinary CLI commands remain
unchanged. No fake success for an empty include graph or wrong command dispatch.
Add unit-hook counter/lifetime evidence for the chosen discovery work policy and
repeat relevant existing filesystem/stack regressions after shared-loop factoring.
Parent decisions needed: command spelling; explicit discovery work/storage policy
above; whether reuse of private DiskProject storage under opaque wrapper is worth
its duplicated captured bytes versus a smaller read-only buffer owner. No source
or compiler work is authorized by this proposal.