77 lines
15 KiB
Markdown
77 lines
15 KiB
Markdown
# S3 original-span composition proposal
|
|
|
|
**Proposal, not an accepted decision or implementation.** Read-only source inspection on 2026-09-22: private analysis `1b1c3e3abb6fc3d0934c9dbfbd21fddc8ae4f594` in `.local/ai3`; ordinary updater at root `4ffff0d9d94bd1875e6b316b84ac11e5c5c9657c`. No compiler, source mutation, public activation, metamodel change or critic delegation. Parent owns adoption/integration.
|
|
|
|
Requirements come from `harness/analysis-source8-20260913/analysis-source8-repair-design-r2-20260913.md`, “Source pipeline and exact editing,” and the S3/S2 handoffs `.local/analysis-source8-s3-index-handoff-20260913.md` / `.local/analysis-source8-s2-implementation-plan-20260913.md`. Earlier plans describe intended interfaces; the implementation facts below take precedence for what currently exists.
|
|
|
|
## Observed seams and smallest useful scope
|
|
|
|
Private `analysis_source/update.rs` already retains qualified occurrence keys and original locations, validates complete simultaneous final namespaces, exact original numeric-file map/bytes, token spelling, complete required edit membership and overlap. It exports only whole-file `Plan::apply`. `Index::parse` consumes typed-only files; `Index::build` is private and trusts its own lowerer's occurrence completeness. `Writer::expression(owner, field, expr, Parent)` exists for later subtree changes; a rename needs no writer invocation.
|
|
|
|
Root `source_project.rs` already collects ordinary original-coordinate `Edits = BTreeMap<LogicalPath, Vec<(Span, String)>>` before `apply_edits` (lines271,679,1050). `SyntaxUpdate` uses complete old/new comparison values plus separate compact insertion values; preserve that distinction. Project-header slices explicitly add the header's file offset once. Current `apply_edits` groups identical spans by concatenating text, including nonempty replacements, and validates/mutates one file at a time. That is not a sufficient mixed-producer validation boundary.
|
|
|
|
After scalar application, `source_structure::update` can reparse and relocate pieces using updated coordinates; it returns immediately when no structural change is needed. Therefore the first integrated slice should explicitly cover stable-ID, topology-preserving global/scoped renames and ordinary scalar edits with disjoint ownership. Reject/defer additions, deletions, moves, reidentity and typed subtree changes at this slice's entry. The existing public updater keeps its current broader behavior until those operations have a compatible composition design; do not claim that its structural second phase already uses the new single-application protocol.
|
|
|
|
## Two candidates
|
|
|
|
| Candidate | Preservation and ownership | Cost / limitation |
|
|
| --- | --- | --- |
|
|
| A. Opaque validated typed edit batch + bundle-owned compositor | Keep Index/Plan provenance; ordinary and typed producers own disjoint spans in one original snapshot. Existing ordinary complete/compact semantics stay in place. | Small adapter and merge boundary; genuine mixed parsing and shared budgets still required before public use. Recommended first slice. |
|
|
| B. Replace both producers with one shared semantic edit-plan/AST representation | Could eventually centralize token, subtree, insertion and relocation ownership. | Requires migrating ordinary EditValue/SyntaxUpdate, structural comment ownership and typed paths at once; substantially more compatibility risk before the first rename benefit. Revisit only when demonstrated structural composition needs justify it. |
|
|
|
|
Applying typed output before ordinary offsets is invalid under either design. Canonicalizing a mixed file as a bridge would destroy required authored preservation.
|
|
|
|
## Candidate A: concrete interface and file plan
|
|
|
|
1. **`analysis_source/update.rs`: export an opaque borrowed batch.** Proposed signatures are sketches, not existing APIs:
|
|
|
|
```rust
|
|
Plan::validated_original_edits(&self, current, budget)
|
|
-> Result<ValidatedRenameEdits<'_>, Error>
|
|
ValidatedRenameEdits::iter(&self)
|
|
-> impl ExactSizeIterator<Item = OriginalNameEdit<'_>>
|
|
```
|
|
|
|
`OriginalNameEdit` exposes read-only `location: {file, original_span}`, `original: &str`, `replacement: &str`, and `occurrence: &OccurrenceKey`. All fields/constructors remain private with getters; records and batch borrow the Plan/index. Validate the whole batch before returning it. The bundle collector accepts the batch itself and iterates every record; it does not accept a caller-selected subset as a complete rename. An empty/no-op batch is valid. Revalidate complete source equality at final application, not merely iterator construction.
|
|
|
|
Factor today's `validate` into (a) ownership/spelling/order/completeness validation and (b) output-length preflight. Keep standalone `Plan::renames`/`apply` behavior. For mixed planning, add a narrowly named composition constructor sharing (a), without checking a fictitious typed-only intermediate output size. Otherwise a typed expansion can be rejected even when a simultaneous ordinary shrink makes the actual final bundle fit. Plan-record/name-memory bounds still apply before allocations, and final combined-output bounds are mandatory. Do not use a fresh Budget to bypass the intermediate check.
|
|
|
|
2. **`source_project/original_edits.rs` (new private module): bind and merge.** A `SourceSnapshot` borrows the complete original `SourceBundle`, including entry path and every file. A private file table maps each numeric typed file key to exactly one canonical LogicalPath from that snapshot, fixed by the parse session. No recomputation from a different traversal and no caller-supplied unchecked file map. The collector stores typed provenance and ordinary owner/role beside each borrowed/owned replacement until validation completes.
|
|
|
|
Collect ordinary `(span, text)` records through an internal adapter that records their expected original substring and source owner; typed records retain their full qualified key. Ordinary owns ordinary headers/fields/references and project header/description; typed owns typed headers and typed occurrences, including references to ordinary targets. Explicitly exclude typed declaration spans from generic SyntaxUpdate so both producers never independently own the same token. A shared physical span is a collision, not a deduplication opportunity.
|
|
|
|
Preflight every file before allocating output: exact entry/files/bytes equality, known mapped file, half-open UTF-8 boundaries, expected old bytes, producer ownership, complete typed set, checked counts/text sizes, sorted disjoint spans, per-file and aggregate final lengths. Reject duplicate nonempty ranges even when their replacements agree. Initially reject ambiguous insertions at a replacement boundary and multiple insertions at one position; existing ordinary insertion groups can enter only after their producer explicitly coalesces them in a defined order into one witnessed insertion. The new boundary must not inherit arbitrary concatenation of equal nonempty spans from `apply_edits`.
|
|
|
|
Emit each file once by copying untouched original slices and final replacements in ascending original order. Return a new candidate bundle only after complete validation; preserve the input on all failures. This is the sole text application for the supported mixed slice. Entry/include strings, authored paths, comments, literal spellings, line endings and untouched files remain exact.
|
|
|
|
3. **`source_project.rs`: route the narrow scalar slice.** Extract/retain ordinary edit collection before the existing application point. Add typed batches there, resolve their file keys through the snapshot, and call the new compositor once. Guard the supported scope before any output construction; no declaration relocation/default-file work is hidden in the adapter. Preserve the stale check that recompiles publicly mutable `CompiledSourceProject` fields and checks index/model/bundle, or replace it only with an equally strong opaque checked-snapshot boundary as part of separately reviewed work.
|
|
|
|
4. **Later `language.rs` / `analysis_source/index.rs` integration:** one opaque parsed-bundle result must carry full-buffer tokens, typed declaration units, lowerer result and file table from the same parse. Construct Index through that provenance, not a newly exposed raw `build(Lowered, spans)` seam. All offsets remain file-relative full-original-buffer byte offsets, including typed declarations after includes or ordinary declarations. Never remove/include-mask bytes, concatenate fragments, or rescan a substring and silently treat its offsets as full-file offsets. Existing local-slice ordinary helpers translate their known base exactly once. No typed rename offset receives a second include/header adjustment.
|
|
|
|
## Namespace, resource and acceptance invariants
|
|
|
|
Global aliases share the complete project namespace. Scoped names are keyed by `(calculation Id, ParameterId)`; stable simultaneous final-name validation must include unchanged ordinary globals and unchanged parameters. Reused parameter UUIDs across calculations and UUID equality with a global remain legal. Occurrence identity retains owner, binding field (including override property), complete structural call path, role and qualified target. Call labels bind to the callee, never caller scope or text alone. Rename-only changes preserve authored parameter/call-argument order; reordering unrelated model vectors is outside the narrow rename plan.
|
|
|
|
Use one inherited parse budget across all reachable ordinary/typed units; charge original bytes/tokens/literals once. Adapter validation does not reset/recharge parsing or numeric work. Combine ordinary+typed edit-record and owned replacement-text counts before reserving the merged list; do not treat each producer passing its own ceiling as aggregate admission. Preflight the complete final bundle against both inherited source8 output bounds and caller ProjectLimits, including unchanged files. A fresh aggregate budget belongs only to the next complete candidate parse. The existing phase byte check is a bounded text-size policy, not an exact heap-allocation accounting claim.
|
|
|
|
For an integrated candidate, recompile the **actual emitted complete bundle**, run existing structural/static admission through its normal validator (no redundant manual second admission), and require the entire resulting Project to equal the independently intended Project before returning/installing source/history. A parseable output that omits an unchanged definition is failure. Pure adapter tests cannot certify this currently unavailable mixed public path. Preserve the closed public dispatch and checked-save boundaries until the parser/lowerer/migration/acceptance work is integrated and verified.
|
|
|
|
## Prerequisites versus deliverables available now
|
|
|
|
**Implementable now on the private analysis branch:** borrowed validated batch export, split validation/preflight internals, and tests for binding/completeness/no output construction. A private composition evaluator can combine this batch with independently authored ordinary edit witnesses and full-buffer expected strings. That demonstrates the merge algorithm, not production ordinary edit generation or mixed compilation. No public schema switch, native GUI, metamodel extension or full S4 completion is needed for this bounded deliverable.
|
|
|
|
**Actually blocking production mixed integration:** root4ffff is still schema7 and has neither the private analysis model/language integration nor the private raw scanner; its tokenizer still rejects typed punctuation/converts ordinary numbers. Reconcile the analysis contribution with current ordinary CompleteForEdit/Compact work instead of replacing current root files wholesale. A real mixed `parse_unit`/edit-position branch, one shared scanner/cursor/bundle budget, full global/signature lowering and opaque Index provenance are required. Stabilize missing global/parameter IDs into exact returned files and reindex before granting edit authority. Complete-output source8 compile/admission, legacy bridge/migration and checked-save acceptance remain required public gates. Root identity correspondence also needs a distinct definition category before enabling reidentity; current fallback category is node placement. These are dependencies, not permission to invent a new model or activate source8 in this adapter.
|
|
|
|
**Not blockers for the read-only batch:** subtree writing, comment salvage for removed nodes, parameter insertion/deletion, declaration movement/new-file ownership, migration activation, evaluation and further metamodel work. Keep them out of the initial patch. The existing Writer supplies the future changed-subtree primitive once its real parent/side context and structural ownership are integrated.
|
|
|
|
## Six independent falsifiers (all proposed, unrun here)
|
|
|
|
1. **Same-file offset cascade.** Independently authored mixed CRLF source with Unicode trivia, an ordinary global header/reference before a calculation and an ordinary scalar field after it. Simultaneously lengthen a global alias, shrink a parameter name and change the later scalar. Assert the exact entire expected file literal, all untouched bytes and intended Project; applying either producer first must fail the oracle.
|
|
2. **Include/file-coordinate binding.** Root includes two nested-path modules; typed and ordinary declarations share a module after an include/comment prefix, and the same numeric offsets occur in another module. Exact expected full bundle verifies edits land in the owning file at its original absolute offset. Wrong/missing/reordered key-to-path mapping rejects before output; include spellings and ownership stay exact.
|
|
3. **Repeated/reordered calls and namespace swaps.** Two calculations reuse parameter IDs/names, one equals a global UUID. Include two reordered calls to one callee and one to the other, with separate comments. Swap the first callee's two parameter names and two global aliases simultaneously. Independently enumerate every declaration/use/label span: four labels for the repeated callee remain distinct; the other callee's labels, strings and argument order remain unchanged. Add a final-name collision negative control.
|
|
4. **Whole-snapshot and Unicode staleness.** After planning, independently mutate an unrelated file/comment, add/remove a file, or change entry while preserving file text. Each fails before output. Separately forge a span inside a multibyte scalar or attach a valid-looking token from another owner; equal spelling/length must not authorize it. A byte-identical current snapshot is the positive control.
|
|
5. **Producer overlap.** Ordinary whole-field replacement contains a typed rename; equal nonempty spans from both producers; duplicate typed occurrence; insertion at a replacement boundary. Each is rejected atomically. Adjacent disjoint ranges and one explicitly coalesced insertion are positives. No replacement concatenation, silent dropping or producer-order choice is allowed.
|
|
6. **Aggregate output and acceptance.** Independent multi-file edits each fit a producer-local ceiling but exceed the combined cap by one byte; require refusal with unchanged input. Exact-cap positive and typed-growth/ordinary-shrink compensation positive enforce final-output accounting. At integration, deliberately omit an otherwise valid unchanged definition/reference edit from the emitted candidate: exact-output recompile/admission plus whole-Project equality must refuse it. No model projection or compiler success alone can pass.
|
|
|
|
All positive adapter fixtures also assert source/numeric usage is unchanged by export/merge and that exporting does not construct whole-file typed output. Integrated variants add complete output recompile, intended-Project equality and history/save refusal on failure.
|