Borrow compact insertion spans and repair omission test oracles

This commit is contained in:
2026-09-13 04:26:59 -07:00
parent adffd19bbc
commit 7bb366f66a
4 changed files with 89 additions and 33 deletions
+20 -20
View File
@@ -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 // declaration field is itself omittable. For example, a selected String type
// needs minimum_length; an Event Signal needs items. The complete tree remains // needs minimum_length; an Event Signal needs items. The complete tree remains
// the comparison authority; this parallel tree is only an insertion template. // the comparison authority; this parallel tree is only an insertion template.
struct InsertionSyntax { struct InsertionSyntax<'a> {
values: BTreeMap<usize, String>, source: &'a str,
values: BTreeMap<usize, Span>,
omitted_fields: BTreeSet<usize>, omitted_fields: BTreeSet<usize>,
} }
impl InsertionSyntax { impl<'a> InsertionSyntax<'a> {
fn new( fn new(
complete: &language::EditValue, complete: &language::EditValue,
compact_values: &language::EditValue, compact_values: &language::EditValue,
source: &str, source: &'a str,
compact: &language::EditValue, compact: &language::EditValue,
) -> Self { ) -> Self {
let mut result = Self { let mut result = Self {
source,
values: BTreeMap::new(), values: BTreeMap::new(),
omitted_fields: BTreeSet::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)) = if let (language::EditKind::Record(all), language::EditKind::Record(kept)) =
(&complete.kind, &compact.kind) (&complete.kind, &compact.kind)
{ {
@@ -1193,21 +1195,15 @@ impl InsertionSyntax {
} }
result result
} }
fn collect( fn collect(&mut self, complete: &language::EditValue, compact: &language::EditValue) {
&mut self, // Borrow the existing compact text once. Copying each containing record
complete: &language::EditValue, // here would duplicate a large nested payload at every depth.
compact: &language::EditValue, self.values.insert(complete.span.start, compact.span);
source: &str,
) {
self.values.insert(
complete.span.start,
source[compact.span.start..compact.span.end].to_owned(),
);
match (&complete.kind, &compact.kind) { match (&complete.kind, &compact.kind) {
(language::EditKind::Record(all), language::EditKind::Record(kept)) => { (language::EditKind::Record(all), language::EditKind::Record(kept)) => {
for (key, field) in all { for (key, field) in all {
if let Some(next) = kept.get(key) { if let Some(next) = kept.get(key) {
self.collect(&field.value, &next.value, source); self.collect(&field.value, &next.value);
} else { } else {
self.omitted_fields.insert(field.value.span.start); self.omitted_fields.insert(field.value.span.start);
} }
@@ -1220,7 +1216,7 @@ impl InsertionSyntax {
"compaction preserves array membership" "compaction preserves array membership"
); );
for (field, next) in all.iter().zip(kept) { 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, source: &'a str,
before: &'a str, before: &'a str,
after: &'a str, after: &'a str,
insertion: Option<&'a InsertionSyntax>, insertion: Option<&'a InsertionSyntax<'a>>,
old_aliases: &'a BTreeMap<String, String>, old_aliases: &'a BTreeMap<String, String>,
new_aliases: &'a BTreeMap<String, String>, new_aliases: &'a BTreeMap<String, String>,
// Source3 migration only adds path qualification; the existing field identity // Source3 migration only adds path qualification; the existing field identity
@@ -1243,8 +1239,12 @@ struct SyntaxUpdate<'a> {
impl SyntaxUpdate<'_> { impl SyntaxUpdate<'_> {
fn inserted(&self, value: &language::EditValue) -> &str { fn inserted(&self, value: &language::EditValue) -> &str {
self.insertion self.insertion
.and_then(|syntax| syntax.values.get(&value.span.start)) .and_then(|syntax| {
.map(String::as_str) syntax
.values
.get(&value.span.start)
.map(|span| &syntax.source[span.start..span.end])
})
.unwrap_or(&self.after[value.span.start..value.span.end]) .unwrap_or(&self.after[value.span.start..value.span.end])
} }
fn header( fn header(
+34 -13
View File
@@ -3,6 +3,16 @@ use oa_core::{language, persistence, validation, *};
fn read(source: &str) -> Project { fn read(source: &str) -> Project {
language::parse(source).unwrap_or_else(|e| panic!("{e:?}\n{source}")) 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) { fn rejected(source: &str, fragment: &str) {
let errors = language::parse(source).expect_err(source); let errors = language::parse(source).expect_err(source);
assert!( assert!(
@@ -753,8 +763,8 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
source.push('}'); source.push('}');
let model = read(&source); let model = read(&source);
let canonical = language::format(&model); let canonical = language::format(&model);
assert!(!canonical.contains("transition_kind External;"));
for fragment in [ for fragment in [
"transition_kind External;",
"transition_kind Local;", "transition_kind Local;",
"transition_kind Internal;", "transition_kind Internal;",
"triggers [@deadline, @assess];", "triggers [@deadline, @assess];",
@@ -776,6 +786,7 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
.find(|r| r.alias == "recover") .find(|r| r.alias == "recover")
.unwrap(); .unwrap();
let RelationBody::StateTransition { let RelationBody::StateTransition {
transition_kind,
trigger, trigger,
guard, guard,
effect, effect,
@@ -784,32 +795,42 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
else { else {
panic!("expected transition") panic!("expected transition")
}; };
assert_eq!(*transition_kind, TransitionKind::External);
assert_eq!(trigger, "Operator confirms\nНе запускать код"); assert_eq!(trigger, "Operator confirms\nНе запускать код");
assert_eq!(guard, "available && status == \"Ready\""); assert_eq!(guard, "available && status == \"Ready\"");
assert_eq!(effect, "Record the assessment"); 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 [ for invalid in [
canonical.replace("transition_kind External;", "transition_kind Unsupported;"), replace_once(
canonical.replace( &canonical,
"triggers [@deadline, @assess];", "triggers [@deadline, @assess];",
"triggers [\"deadline\", @assess];", "triggers [\"deadline\", @assess];",
), ),
canonical.replace( replace_once(
&canonical,
"triggers [@deadline, @assess];", "triggers [@deadline, @assess];",
"triggers [@deadline, @deadline];", "triggers [@deadline, @deadline];",
), ),
canonical.replace("effects [@assess];", "effects [@payload];"), replace_once(&canonical, "effects [@assess];", "effects [@payload];"),
canonical.replace("kind After;", "kind Relative;"), replace_once(&canonical, "kind After;", "kind Relative;"),
canonical.replace("items [@payload];", "items [@assess];"), replace_once(&canonical, "items [@payload];", "items [@assess];"),
] { ] {
assert!( assert!(
language::parse(&invalid).is_err(), language::parse(&invalid).is_err(),
"invalid typed state/event source accepted" "invalid typed state/event source accepted"
); );
} }
let legacy = canonical // Canonical source already omits External. Removing the supplied lists
.replace("transition_kind External;", "") // independently exercises their empty omission defaults and retained prose.
.replace("triggers [@deadline, @assess];", "") let legacy = replace_once(&canonical, "triggers [@deadline, @assess];", "");
.replace("effects [@assess];", ""); let legacy = replace_once(&legacy, "effects [@assess];", "");
let legacy = read(&legacy); let legacy = read(&legacy);
let RelationBody::StateTransition { let RelationBody::StateTransition {
transition_kind, transition_kind,
@@ -829,7 +850,7 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
(retained_trigger, retained_guard, retained_effect), (retained_trigger, retained_guard, retained_effect),
(trigger, guard, 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()); assert!(language::parse(&draft).is_ok());
let deadline = model let deadline = model
.elements .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" "fixture must cover actual old scenario content"
); );
let canonical = language::format(&from_source); let canonical = language::format(&from_source);
assert!(canonical.contains("viewpoint MixedLegacy;")); assert!(!canonical.contains("viewpoint MixedLegacy;"));
for relation in from_source.relations.values() { for relation in from_source.relations.values() {
if let RelationBody::StateTransition { if let RelationBody::StateTransition {
transition_kind, transition_kind,
@@ -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.
@@ -1,5 +1,7 @@
# Ordinary compact source: production checkpoint, verification incomplete # 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. 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 ## Behavior and architecture