Integrate nullable stored-history constructor correction

This commit is contained in:
2026-09-10 11:25:59 -07:00
6 changed files with 241 additions and 2 deletions
+3
View File
@@ -449,6 +449,9 @@ impl Draft {
None
};
let tag = |value: &Value| {
if self.kind == RecordKind::Trace && history_fields::is_path(path) {
return history_fields::constructor_tag(path, value);
}
value
.as_str()
.or_else(|| value.as_object()?.keys().next().map(String::as_str))
@@ -382,6 +382,45 @@ fn branch_has_fields(schema: &'static Value) -> bool {
.is_none_or(|values| values.len() > 1)
}
/// Constructor identity is independent of a primitive's edited content. In
/// particular, absent, an empty UUID/text, a number and a list are different
/// branches. String enum tags and externally tagged objects retain their variant.
pub(super) fn constructor_tag(path: &str, value: &Value) -> String {
fn enum_value(schema: &'static Value, value: &Value, depth: usize) -> bool {
if depth > MAX_DEPTH {
return false;
}
let Ok(schema) = resolve(schema) else {
return false;
};
if let Some(options) = alternatives(schema) {
return options
.iter()
.any(|option| enum_value(option, value, depth + 1));
}
schema
.get("enum")
.and_then(Value::as_array)
.is_some_and(|values| values.contains(value))
}
match value {
Value::Null => "null".into(),
Value::Bool(_) => "boolean".into(),
Value::Number(_) => "number".into(),
Value::Array(_) => "array".into(),
Value::String(word)
if schema_at(path).is_some_and(|schema| enum_value(schema, value, 0)) =>
{
format!("enum:{word}")
}
Value::String(_) => "string".into(),
Value::Object(fields) => format!(
"object:{}",
fields.keys().cloned().collect::<Vec<_>>().join("/")
),
}
}
fn paging<'a>(draft: &'a Draft, path: &str, count: usize) -> (usize, View<'a, Message>) {
let last = count.saturating_sub(1) / PAGE_SIZE;
let page = draft
@@ -731,3 +731,181 @@ fn history_clear_discards_inactive_invalid_constructors_before_reusing_the_same_
});
assert_history_transaction(&mut document(&project), &draft, &expected);
}
/// Exercise the same descriptor choice and FieldShape event emitted by the
/// nested picker. Injecting an already complete Observed value skips this path.
fn choose_history_constructor(draft: &mut Draft, path: &str, label: &str) {
let schema = resolve(schema_at(path).expect("typed history field")).unwrap();
let branch = alternatives(schema)
.expect("constructor choices")
.iter()
.find(|branch| branch_label(branch) == label)
.expect("visible constructor choice");
let event = variant_message(
path,
branch_seed(branch).unwrap(),
draft.value.pointer(path).is_some(),
);
let Message::FieldShape(path, value) = event else {
panic!("constructor picker must emit FieldShape");
};
draft.shape(&path, value).unwrap();
}
#[test]
fn history_nested_optional_number_events_switch_absent_present_and_recover_invalid_raw() {
let (project, id) = fixture(Some(baseline(vec![])));
let mut draft = Draft::from_project(&project, id).unwrap();
draft
.append(
FACETS,
choice(FacetSelector::Data(DataFacet::IntegerMinimum)),
)
.unwrap();
let side = format!("{FACETS}/0/Data/IntegerMinimum/oa");
let path = format!("{side}/Value");
choose_history_constructor(&mut draft, &side, "Observed value");
assert_eq!(draft.value.pointer(&path), Some(&Value::Null));
choose_history_constructor(&mut draft, &path, "Set number");
assert_eq!(
draft.value.pointer(&path),
Some(&json!(0)),
"null and numeric constructor identities must differ"
);
draft.input(&path, "-".into());
choose_history_constructor(&mut draft, &path, "Absent (none)");
assert_eq!(draft.value.pointer(&path), Some(&Value::Null));
let mut draft = Draft::from_recovery(&project, draft.recovery_snapshot()).unwrap();
choose_history_constructor(&mut draft, &path, "Set number");
assert_eq!(draft.inputs[&path].raw, "-");
assert!(draft.command().is_err());
draft.input(&path, "7".into());
let mut expected = project.clone();
expected
.traces
.get_mut(&id)
.unwrap()
.baseline
.as_mut()
.unwrap()
.semantic = Some(SemanticBaseline {
facets: vec![FacetReview::Data(DataReview::IntegerMinimum(Reviewed {
oa: Observed::Value(Some(7)),
sa: Observed::NotApplicable,
}))],
});
assert_history_transaction(&mut document(&project), &draft, &expected);
choose_history_constructor(&mut draft, &path, "Absent (none)");
expected
.traces
.get_mut(&id)
.unwrap()
.baseline
.as_mut()
.unwrap()
.semantic = Some(SemanticBaseline {
facets: vec![FacetReview::Data(DataReview::IntegerMinimum(Reviewed {
oa: Observed::Value(None),
sa: Observed::NotApplicable,
}))],
});
assert_history_transaction(&mut document(&project), &draft, &expected);
choose_history_constructor(&mut draft, &path, "Set number");
assert_eq!(
draft.inputs[&path].raw, "7",
"returning to Value restores its edited contents"
);
}
#[test]
fn history_nested_uuid_and_list_events_preserve_present_values_and_tagged_item_branches() {
let (project, id) = fixture(Some(baseline(vec![])));
let mut draft = Draft::from_project(&project, id).unwrap();
draft
.append(FACETS, choice(FacetSelector::Header(HeaderFacet::Parent)))
.unwrap();
draft
.append(FACETS, choice(FacetSelector::Field(FieldFacet::Default)))
.unwrap();
let side = format!("{FACETS}/0/Header/Parent/oa");
let uuid = format!("{side}/Value");
choose_history_constructor(&mut draft, &side, "Observed value");
choose_history_constructor(&mut draft, &uuid, "Set historical UUID");
assert_eq!(
draft.value.pointer(&uuid),
Some(&json!("")),
"empty UUID input is distinct from absent"
);
assert!(draft.command().is_err());
draft.input(&uuid, "incomplete-uuid".into());
choose_history_constructor(&mut draft, &uuid, "Absent (none)");
choose_history_constructor(&mut draft, &uuid, "Set historical UUID");
assert_eq!(draft.inputs[&uuid].raw, "incomplete-uuid");
let historical = Id::from_u128(8765);
draft.input(&uuid, historical.to_string());
choose_history_constructor(&mut draft, &uuid, "Absent (none)");
choose_history_constructor(&mut draft, &uuid, "Set historical UUID");
assert_eq!(draft.inputs[&uuid].raw, historical.to_string());
let side = format!("{FACETS}/1/Field/Default/oa");
let list = format!("{side}/Value");
choose_history_constructor(&mut draft, &side, "Observed value");
choose_history_constructor(&mut draft, &list, "Set list");
assert_eq!(
draft.value.pointer(&list),
Some(&json!([])),
"empty list is distinct from absent"
);
let array_schema = alternatives(resolve(schema_at(&list).unwrap()).unwrap())
.unwrap()
.iter()
.map(|branch| resolve(branch).unwrap())
.find(|branch| branch["type"] == "array")
.unwrap();
let event = Message::FieldAppend(list.clone(), seed(&array_schema["items"], 0).unwrap());
let Message::FieldAppend(path, value) = event else {
unreachable!()
};
draft.append(&path, value).unwrap();
let item = format!("{list}/0");
choose_history_constructor(&mut draft, &item, "Integer");
draft.input(&format!("{item}/Integer"), "-".into());
choose_history_constructor(&mut draft, &item, "String");
draft.input(&format!("{item}/String"), "retained textual branch".into());
choose_history_constructor(&mut draft, &item, "Integer");
assert_eq!(draft.inputs[&format!("{item}/Integer")].raw, "-");
draft.input(&format!("{item}/Integer"), "21".into());
choose_history_constructor(&mut draft, &list, "Absent (none)");
assert_eq!(draft.value.pointer(&list), Some(&Value::Null));
let mut draft = Draft::from_recovery(&project, draft.recovery_snapshot()).unwrap();
choose_history_constructor(&mut draft, &list, "Set list");
assert_eq!(draft.inputs[&format!("{item}/Integer")].raw, "21");
choose_history_constructor(&mut draft, &item, "String");
assert_eq!(
draft.inputs[&format!("{item}/String")].raw,
"retained textual branch"
);
let mut expected = project.clone();
expected
.traces
.get_mut(&id)
.unwrap()
.baseline
.as_mut()
.unwrap()
.semantic = Some(SemanticBaseline {
facets: vec![
FacetReview::Header(HeaderReview::Parent(Reviewed {
oa: Observed::Value(Some(historical)),
sa: Observed::NotApplicable,
})),
FacetReview::Field(FieldReview::Default(Reviewed {
oa: Observed::Value(Some(vec![DataValue::String(
"retained textual branch".into(),
)])),
sa: Observed::NotApplicable,
})),
],
});
assert_history_transaction(&mut document(&project), &draft, &expected);
}
+7 -2
View File
@@ -13,8 +13,13 @@ its cold toolchain/process claims cannot be mistaken for this host's state.
Its optional history-value constructor cache defect has a follow-up pending.
No integrated compile/native pass is claimed. Scenario layout follows the merge;
the checkpoint worker independently restores inline name focus after recovery.
The user subsequently reauthorized Computer Use explicitly. Root will resume
native checks against separately staged current binaries after compilation.
The user subsequently reauthorized Computer Use explicitly. Candidate 2 native
F2/Save/Undo/Redo now passes the independent exact name-only three-source delta
and the full Entity Undo oracle. Recovery autofocus is committed in `31b7dc3`;
its 13 focused tests passed after integrated compilation. The immutable 618-test
artifact from `057eddd` plus that checkpoint patch is running separately from
current source edits. The verified follow-up bundle `b6b76ef` now supplies the
F1 nullable-constructor correction; its additional tests await the next build.
Latest checkpoint (18:10 UTC, Codex-9e8r6t): palette compile repairs, bounded
inline naming, stale-submit retention and focused first-Escape cancellation are
@@ -86,3 +86,5 @@ Root authorized the bounded implementation in `crates/oa-desktop/src/forms.rs`,
Ten targeted tests are present. They cover absent/empty/existing history; descriptor-derived complete witnesses for every offered scalar/keyed fact against actual FacetReview deserialization and historical validation; rejected NA/NA and duplicates with unchanged Document snapshots; deleted UUID keys/observations; numeric limits and invalid raw repair; compound values and constructor switching; TypeRef's nested primitive picker; exact Unicode/quoted strings and literal keys; cache relocation/recovery; clear/recreate isolation; source/JSON round-trip and exact one-step Undo/Redo including retained source comments.
**Verification status:** `git diff --check` passes for the candidate (an unrelated generated HTML line-ending warning is present). No Cargo, rustfmt, native GUI or integration suite was run by this worker; root owns the serialized build/toolchain lease. F1 is therefore a ready source candidate, not an accepted fix yet. Root should run targeted history tests, the relevant desktop suite and strict lint, then exercise expanded history controls at normal/minimum width, typed Add/repair, paging, apply/reopen and Undo in the actual desktop. F2 and shared decision/wiki synthesis remain root-owned. F3 remains open.
**Independent-review follow-up:** behavior_audit found a real defect in snapshot `01bb52a`: the reused constructor-cache key mapped null, numbers, booleans and arrays to the same empty tag, also colliding with empty UUID input. The nested optional picker consequently restored the old branch immediately instead of switching; earlier tests that injected complete Observed values skipped that event. The bounded follow-up adds history-only typed constructor identities (including distinct enum and tagged-object variants) while retaining other property-editor behavior. Two additional tests derive the visible nested picker choices and dispatch their actual FieldShape events: absent/present integer with invalid raw recovery and exact Value(None); empty UUID/absence with deleted identity; optional list/absence with nested Integer/String constructor retention and recovery. The resulting accepted models still receive exact Document/source/Undo checks. Twelve history tests are now present. Whitespace check passes; compilation, execution and native acceptance remain pending with root.
@@ -2,6 +2,18 @@
The following sections are preserved from Codex-956atf commit 01bb52a during integration on Codex-9e8r6t. Paths, process handles, toolchains and validation statements describe that separate host at that commit; they are not current local evidence. See STATE.md for current local status.
Follow-up `b6b76ef` fixes the independently identified optional-value constructor
cache collision and expands stored-history coverage from ten to twelve tests.
Its host ledger records an anonymous fetch of our palette `7d1df4f`, an earlier
full-platform dependency fetch stopped after network timeouts, and a cold Windows
desktop compilation under its own exec handle 51036. That handle is not local.
Neither their source review nor bundle transfer establishes a test pass.
The follow-up bundle includes both commits from prerequisite `1797516`, is
60,757 bytes, and has SHA256
`2CC21D7B1B8D9F5895C7239F5C37615C4167E732A14EA5480CBD4ECB99CAE45B`.
Root verified the decoded bundle and is merging the correction separately from
the immutable earlier 618-test artifact. Original host-specific history follows.
## STATE
The renewed user request is preserved in `PROJECT_GOAL.md`. Root is integrating