From 7bb366f66a8c6f8b6f66fc9b04c9e3b39f84b78e Mon Sep 17 00:00:00 2001 From: "sorokin.oleg.edu" Date: Sun, 13 Sep 2026 04:26:59 -0700 Subject: [PATCH] Borrow compact insertion spans and repair omission test oracles --- crates/oa-core/src/source_project.rs | 40 ++++++++-------- crates/oa-core/tests/language.rs | 47 ++++++++++++++----- ...text-default-omission-followup-20260913.md | 33 +++++++++++++ ...xt-default-omission-production-20260913.md | 2 + 4 files changed, 89 insertions(+), 33 deletions(-) create mode 100644 harness/text-default-omission-followup-20260913.md diff --git a/crates/oa-core/src/source_project.rs b/crates/oa-core/src/source_project.rs index 55c82dc7..22575d07 100644 --- a/crates/oa-core/src/source_project.rs +++ b/crates/oa-core/src/source_project.rs @@ -1166,22 +1166,24 @@ fn inserted_value(text: &str, indent: &str, source: &str) -> String { // declaration field is itself omittable. For example, a selected String type // needs minimum_length; an Event Signal needs items. The complete tree remains // the comparison authority; this parallel tree is only an insertion template. -struct InsertionSyntax { - values: BTreeMap, +struct InsertionSyntax<'a> { + source: &'a str, + values: BTreeMap, omitted_fields: BTreeSet, } -impl InsertionSyntax { +impl<'a> InsertionSyntax<'a> { fn new( complete: &language::EditValue, compact_values: &language::EditValue, - source: &str, + source: &'a str, compact: &language::EditValue, ) -> Self { let mut result = Self { + source, values: BTreeMap::new(), omitted_fields: BTreeSet::new(), }; - result.collect(complete, compact_values, source); + result.collect(complete, compact_values); if let (language::EditKind::Record(all), language::EditKind::Record(kept)) = (&complete.kind, &compact.kind) { @@ -1193,21 +1195,15 @@ impl InsertionSyntax { } result } - fn collect( - &mut self, - complete: &language::EditValue, - compact: &language::EditValue, - source: &str, - ) { - self.values.insert( - complete.span.start, - source[compact.span.start..compact.span.end].to_owned(), - ); + fn collect(&mut self, complete: &language::EditValue, compact: &language::EditValue) { + // Borrow the existing compact text once. Copying each containing record + // here would duplicate a large nested payload at every depth. + self.values.insert(complete.span.start, compact.span); match (&complete.kind, &compact.kind) { (language::EditKind::Record(all), language::EditKind::Record(kept)) => { for (key, field) in all { if let Some(next) = kept.get(key) { - self.collect(&field.value, &next.value, source); + self.collect(&field.value, &next.value); } else { self.omitted_fields.insert(field.value.span.start); } @@ -1220,7 +1216,7 @@ impl InsertionSyntax { "compaction preserves array membership" ); for (field, next) in all.iter().zip(kept) { - self.collect(field, next, source); + self.collect(field, next); } } _ => {} @@ -1232,7 +1228,7 @@ struct SyntaxUpdate<'a> { source: &'a str, before: &'a str, after: &'a str, - insertion: Option<&'a InsertionSyntax>, + insertion: Option<&'a InsertionSyntax<'a>>, old_aliases: &'a BTreeMap, new_aliases: &'a BTreeMap, // Source3 migration only adds path qualification; the existing field identity @@ -1243,8 +1239,12 @@ struct SyntaxUpdate<'a> { impl SyntaxUpdate<'_> { fn inserted(&self, value: &language::EditValue) -> &str { self.insertion - .and_then(|syntax| syntax.values.get(&value.span.start)) - .map(String::as_str) + .and_then(|syntax| { + syntax + .values + .get(&value.span.start) + .map(|span| &syntax.source[span.start..span.end]) + }) .unwrap_or(&self.after[value.span.start..value.span.end]) } fn header( diff --git a/crates/oa-core/tests/language.rs b/crates/oa-core/tests/language.rs index b598ed8a..d18394f8 100644 --- a/crates/oa-core/tests/language.rs +++ b/crates/oa-core/tests/language.rs @@ -3,6 +3,16 @@ use oa_core::{language, persistence, validation, *}; fn read(source: &str) -> Project { language::parse(source).unwrap_or_else(|e| panic!("{e:?}\n{source}")) } +fn replace_once(source: &str, before: &str, after: &str) -> String { + assert_eq!( + source.matches(before).count(), + 1, + "unique mutation anchor {before:?}" + ); + let changed = source.replacen(before, after, 1); + assert_ne!(changed, source, "test mutation must change the source"); + changed +} fn rejected(source: &str, fragment: &str) { let errors = language::parse(source).expect_err(source); assert!( @@ -753,8 +763,8 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() { source.push('}'); let model = read(&source); let canonical = language::format(&model); + assert!(!canonical.contains("transition_kind External;")); for fragment in [ - "transition_kind External;", "transition_kind Local;", "transition_kind Internal;", "triggers [@deadline, @assess];", @@ -776,6 +786,7 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() { .find(|r| r.alias == "recover") .unwrap(); let RelationBody::StateTransition { + transition_kind, trigger, guard, effect, @@ -784,32 +795,42 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() { else { panic!("expected transition") }; + assert_eq!(*transition_kind, TransitionKind::External); assert_eq!(trigger, "Operator confirms\nНе запускать код"); assert_eq!(guard, "available && status == \"Ready\""); assert_eq!(effect, "Record the assessment"); + // External is omitted in compact output. Insert an explicit invalid + // discriminator into Recover using its unique, nondefault trigger list. + let invalid_kind = replace_once( + &canonical, + "triggers [@deadline, @assess];", + "transition_kind Unsupported;\n triggers [@deadline, @assess];", + ); + rejected(&invalid_kind, "Unsupported"); for invalid in [ - canonical.replace("transition_kind External;", "transition_kind Unsupported;"), - canonical.replace( + replace_once( + &canonical, "triggers [@deadline, @assess];", "triggers [\"deadline\", @assess];", ), - canonical.replace( + replace_once( + &canonical, "triggers [@deadline, @assess];", "triggers [@deadline, @deadline];", ), - canonical.replace("effects [@assess];", "effects [@payload];"), - canonical.replace("kind After;", "kind Relative;"), - canonical.replace("items [@payload];", "items [@assess];"), + replace_once(&canonical, "effects [@assess];", "effects [@payload];"), + replace_once(&canonical, "kind After;", "kind Relative;"), + replace_once(&canonical, "items [@payload];", "items [@assess];"), ] { assert!( language::parse(&invalid).is_err(), "invalid typed state/event source accepted" ); } - let legacy = canonical - .replace("transition_kind External;", "") - .replace("triggers [@deadline, @assess];", "") - .replace("effects [@assess];", ""); + // Canonical source already omits External. Removing the supplied lists + // independently exercises their empty omission defaults and retained prose. + let legacy = replace_once(&canonical, "triggers [@deadline, @assess];", ""); + let legacy = replace_once(&legacy, "effects [@assess];", ""); let legacy = read(&legacy); let RelationBody::StateTransition { transition_kind, @@ -829,7 +850,7 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() { (retained_trigger, retained_guard, retained_effect), (trigger, guard, effect) ); - let draft = canonical.replace("expression \"30 s\";", "expression \"\";"); + let draft = replace_once(&canonical, "expression \"30 s\";", "expression \"\";"); assert!(language::parse(&draft).is_ok()); let deadline = model .elements @@ -1038,7 +1059,7 @@ fn original_v2_text_and_json_preserve_identity_and_mixed_scenarios_after_extensi "fixture must cover actual old scenario content" ); let canonical = language::format(&from_source); - assert!(canonical.contains("viewpoint MixedLegacy;")); + assert!(!canonical.contains("viewpoint MixedLegacy;")); for relation in from_source.relations.values() { if let RelationBody::StateTransition { transition_kind, diff --git a/harness/text-default-omission-followup-20260913.md b/harness/text-default-omission-followup-20260913.md new file mode 100644 index 00000000..4eb7da77 --- /dev/null +++ b/harness/text-default-omission-followup-20260913.md @@ -0,0 +1,33 @@ +# Ordinary default omission: reviewed follow-up + +This branch-specific follow-up is based on production checkpoint `adffd19bbcf55c7cbfbf7a3937629db4c593e387`. It changes insertion storage and two language-test expectations. It is **not compiled, runtime-verified, integrated, or release-ready**. Previous runtime results in the [production report](text-default-omission-production-20260913.md) apply only to their captured inputs. + +## Changes and independent review + +`crates/oa-core/src/source_project.rs` now stores compact insertion spans and one borrowed source string instead of copying each value's substring. Keys still identify complete-after value starts; spans originate from parsing that exact compact string. Record/list matching, omitted-field decisions, fallback values, and final edit construction are unchanged. This removes repeated containing-payload copies by construction; no performance or memory benchmark is claimed. + +The independent static critic checked parser-derived UTF-8 boundaries, source ownership, covariance and local borrow lifetimes. The compact string owner outlives each insertion/update operation, and copied spans do not borrow temporary syntax trees. No blocking static finding was reported. Rust type checking remains required. + +`crates/oa-core/tests/language.rs` now expects canonical omission of External transition kind and MixedLegacy viewpoint. Existing whole-model, identity, legacy-default and round-trip checks remain. The invalid transition-kind test explicitly inserts Unsupported at a unique retained trigger-list anchor and checks the diagnostic. Every changed mutation checks that its anchor occurs exactly once and that the resulting source differs, preventing a silently ineffective replacement of an omitted field. + +The independent critic reviewed both frozen files and found no blocking oracle defect. The parent inspected the actual diff and confirmed the reviewed hashes: + +| File | SHA-256 | +| --- | --- | +| `crates/oa-core/src/source_project.rs` | `ee925b142c3072e2ad800922f43b6569a5c8537a037a6c0cd7533d9b9edefa6b` | +| `crates/oa-core/tests/language.rs` | `9ad8bd0d3804e75e1aefedb93895053e1ef27647c6169bc38a33a7d5950d7801` | + +## Verification boundary + +On Windows with the existing Rust 1.98.1 GNU LLVM toolchain, these checks passed for the two files above: + +```text +rustfmt --edition 2024 --config skip_children=true --check crates/oa-core/src/source_project.rs crates/oa-core/tests/language.rs +git diff --check +``` + +No Cargo build or runtime gate was started for this follow-up. After the user reported cleanup, C: still reported approximately 4 MB free and the named retired build artifacts remained present. That observation does not establish which earlier cleanup commands ran. The user was asked for the latest manual script's output; the agent did not retry rejected deletion. + +Required next execution is the changed language tests, source-update/preservation regressions, corrected port-anchor test including re-add behavior, and private policy/parser tests, followed by the full core suite and strict production Clippy. Retain the existing exact-input capture workflow and preserve failed results. GUI/public-consumer and combined source8 verification remain separate acceptance requirements. Linux remains user-paused. + +Live ownership, blockers and handoffs remain in Gitea issue 11; the source8 partition remains in issue 7. This note records the changed artifact and evidence boundary rather than a replacement task ledger. diff --git a/harness/text-default-omission-production-20260913.md b/harness/text-default-omission-production-20260913.md index 2937ce26..ae0fb6d1 100644 --- a/harness/text-default-omission-production-20260913.md +++ b/harness/text-default-omission-production-20260913.md @@ -1,5 +1,7 @@ # Ordinary compact source: production checkpoint, verification incomplete +The [later reviewed follow-up](text-default-omission-followup-20260913.md) implements borrowed insertion spans and repairs language-test expectations. Its runtime verification is pending; the evidence below describes the original checkpoint. + Branch: `codex/text-defaults-9e8r6t`, based on `2a64344b929077c873cb81c4f0b36c6bb3ae1ffe`. This checkpoint implements ordinary source defaults and lossless editing, but is **not ready for integration or release acceptance**. The focused production run passed 75 tests. The full core build exhausted disk space before all targets linked. Subsequent execution of 36 completed integration binaries yielded 354 passed, 2 failed, 1 ignored; one failure was subsequently resolved and rerun, while the other has a reviewed test correction awaiting compilation. ## Behavior and architecture