Files
ARCADIA-Studio/crates/oa-desktop/src/semantic_review/compose.rs
T
Codex e2ecfea69b
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Checkpoint source 7 workflows and in-progress diagram authoring redesign
Preserve current core, source, REST, desktop, recovery, documentation and validation work, including AGENTS.md and the development summary. This is a work-in-progress checkpoint: the new compact palette has outstanding compilation errors and native acceptance; earlier passing gates retain their documented scope.
2026-09-10 10:06:55 -07:00

689 lines
26 KiB
Rust

//! Compare independent authored intent before building the core's unique target candidates.
use super::*;
#[cfg(test)]
#[path = "compose_diagnostic_tests.rs"]
mod diagnostic_tests;
type Slot = (Id, FacetSelector);
fn context_leaf_path(project: &Project, slot: &Slot) -> Option<&'static str> {
if project.relations.get(&slot.0)?.kind() != RelationKind::ContextualLink {
return None;
}
match slot.1 {
FacetSelector::Relation(RelationFacet::Source) => {
Some("/body/ContextualLink/source/occurrence")
}
FacetSelector::Relation(RelationFacet::Target) => {
Some("/body/ContextualLink/target/occurrence")
}
_ => None,
}
}
struct Intent {
value: Value,
row: RowAddress,
origin: FacetSelector,
}
struct Proposal {
address: RowAddress,
value: Value,
groups: Vec<FacetSelector>,
copies: Vec<FacetSelector>,
}
fn canonical(facet: &FacetSelector, layer: Layer) -> Option<FacetSelector> {
fn key<T: Clone>(keys: &KeyPair<T>, layer: Layer) -> Option<KeyPair<T>> {
Some(KeyPair {
oa: None,
sa: Some(
if layer == Layer::Oa {
keys.oa.as_ref()?
} else {
keys.sa.as_ref()?
}
.clone(),
),
})
}
Some(match facet {
FacetSelector::Member { collection, keys } => FacetSelector::Member {
collection: *collection,
keys: key(keys, layer)?,
},
FacetSelector::Literal(keys) => FacetSelector::Literal(key(keys, layer)?),
FacetSelector::Annotation(keys) => FacetSelector::Annotation(key(keys, layer)?),
FacetSelector::Binding(keys) => FacetSelector::Binding(key(keys, layer)?),
FacetSelector::Child(_)
| FacetSelector::Incident { .. }
| FacetSelector::Artifact { .. } => return None,
_ => facet.clone(),
})
}
fn observe(project: &Project, slot: &Slot) -> Result<Value, String> {
if let Some(path) = context_leaf_path(project, slot) {
let value = record_value(project, slot.0)?
.pointer(path)
.cloned()
.ok_or("Qualified endpoint leaf is absent.")?;
return Ok(serde_json::json!({"Value":value}));
}
if let Some((_, facet)) = artifacts::parts(&slot.1) {
return Ok(artifacts::observation(
project.artifact_links.get(&slot.0),
facet,
));
}
// A retired destination slot can legitimately apply on neither side of this
// synthetic target-only observation. Public capture_facet intentionally rejects
// such historical facts; determine absence through the same closed UI binding.
let applicable = if let Some(family) = keyed::family(&slot.1) {
keyed::path(&record_value(project, slot.0)?, family).is_some()
} else if let FacetSelector::Order(which) = slot.1 {
ordered::read(&record_value(project, slot.0)?, which).is_ok()
} else {
scalar::binding(project, slot.0, &slot.1).is_some()
};
if !applicable {
return Ok(Value::String("NotApplicable".into()));
}
Ok(scalar::observation(
&capture_facet(project, slot.0, slot.0, &slot.1)?,
Layer::Sa,
))
}
fn record_value(project: &Project, id: Id) -> Result<Value, String> {
Draft::from_project(project, id)
.map(|draft| draft.value)
.ok_or("Target has no editable semantic record.".into())
}
fn install(project: &mut Project, id: Id, value: Value) -> Result<(), String> {
if let std::collections::btree_map::Entry::Occupied(mut entry) = project.elements.entry(id) {
let record: Element = serde_json::from_value(value)
.map_err(|error| format!("Final target value: {error}"))?;
if record.id != id {
return Err("Candidate identity changed.".into());
}
entry.insert(record);
} else if let std::collections::btree_map::Entry::Occupied(mut entry) =
project.relations.entry(id)
{
let record: Relation = serde_json::from_value(value)
.map_err(|error| format!("Final target value: {error}"))?;
if record.id != id {
return Err("Candidate identity changed.".into());
}
entry.insert(record);
} else if let std::collections::btree_map::Entry::Occupied(mut entry) =
project.artifact_links.entry(id)
{
let record: ArtifactLink = serde_json::from_value(value)
.map_err(|error| format!("Final artifact value: {error}"))?;
if record.id != id {
return Err("Candidate artifact identity changed.".into());
}
entry.insert(record);
} else {
return Err("The batch foundation requires an existing target record.".into());
}
Ok(())
}
fn identity(base: &Project, id: Id) -> String {
base.identities()
.into_iter()
.find(|(key, _)| *key == id)
.map_or_else(
|| format!("Missing identity {id}"),
|(_, alias)| format!("{} · @{alias}", base.named(id)),
)
}
fn review_identity(state: &State, row: RowAddress, id: Id) -> String {
if !state.base.contains(id)
&& let Some(review) = state.row(row)
&& review.artifacts.seeds.contains_key(&id)
{
return review.review_choice(&state.base, id).label;
}
identity(&state.base, id)
}
fn review_title(state: &State, row: RowAddress, facet: &FacetSelector) -> String {
state.row(row).map_or_else(
|| view::title(&state.base, facet),
|review| review.review_title(&state.base, facet),
)
}
#[cfg(test)]
fn describe(base: &Project, slot: &Slot, value: &Value) -> String {
describe_with(slot, value, |id| identity(base, id))
}
fn describe_with(slot: &Slot, value: &Value, identify: impl Fn(Id) -> String) -> String {
let text = if let Some((_, facet)) = artifacts::parts(&slot.1) {
if value.as_str() == Some("NotApplicable") {
"artifact absent".into()
} else if facet == ArtifactFacet::Owner {
value
.get("Value")
.and_then(Value::as_str)
.and_then(|s| s.parse::<Id>().ok())
.map(|id| format!("owner {}", identify(id)))
.unwrap_or_else(|| value.to_string())
} else {
value.get("Value").unwrap_or(value).to_string()
}
} else {
value.to_string()
};
if text.chars().count() > 400 {
format!(
"{}… (full draft retained)",
text.chars().take(400).collect::<String>()
)
} else {
text
}
}
fn agree(
state: &State,
slots: &mut BTreeMap<Slot, Intent>,
slot: Slot,
value: Value,
row: RowAddress,
origin: &FacetSelector,
) -> Result<(), String> {
let base = &state.base;
if let Some(previous) = slots.get(&slot) {
if previous.value != value {
let same_row = previous.row == row;
let heading = if same_row {
"Decisions in this review disagree"
} else {
"Independent row proposals disagree"
};
let scope = artifacts::parts(&slot.1).map_or_else(
|| view::title(base, &slot.1),
|(_, facet)| format!("Artifact {facet:?}"),
);
let first_row = format!(
"{} → {} [trace {}]",
identity(base, previous.row.source),
identity(base, previous.row.target),
identity(base, previous.row.trace)
);
let second_row = if same_row {
"the same review".into()
} else {
format!(
"{} → {} [trace {}]",
identity(base, row.source),
identity(base, row.target),
identity(base, row.trace)
)
};
let action = if same_row {
"Edit or unselect one of these decisions."
} else {
"Edit or unselect a decision, or exclude a row."
};
return Err(format!(
"{heading} for {} · {scope}: {} in {first_row} requires {}; {} in {second_row} requires {}. {action} Both drafts are retained.",
review_identity(state, row, slot.0),
review_title(state, previous.row, &previous.origin),
describe_with(&slot, &previous.value, |id| review_identity(
state,
previous.row,
id
)),
review_title(state, row, origin),
describe_with(&slot, &value, |id| review_identity(state, row, id))
));
}
} else {
slots.insert(
slot,
Intent {
value,
row,
origin: origin.clone(),
},
);
}
Ok(())
}
fn write_slot(project: &mut Project, slot: &Slot, observation: &Value) -> Result<(), String> {
if let Some((_, facet)) = artifacts::parts(&slot.1) {
if observation.as_str() == Some("NotApplicable") {
if facet == ArtifactFacet::Owner {
project.artifact_links.remove(&slot.0);
}
return Ok(());
}
let mut value = record_value(project, slot.0)?;
let field =
artifacts::field(facet).ok_or("Artifact actual slot must identify metadata.")?;
value[field] = observation
.get("Value")
.cloned()
.ok_or("Artifact observation lacks typed value.")?;
return install(project, slot.0, value);
}
if scalar::grouped(&slot.1) || observation.as_str() == Some("NotApplicable") {
return Ok(());
}
let supplied = observation
.get("Value")
.ok_or("Missing typed destination observation.")?
.clone();
let mut value = record_value(project, slot.0)?;
if keyed_rows::supported(&slot.1) {
let key = keyed::key(&slot.1, Layer::Sa).ok_or("Destination row key missing.")?;
let path = keyed::path(&value, keyed::family(&slot.1).expect("Supported family"))
.ok_or("Destination row collection is no longer applicable.")?;
let rows = value
.pointer_mut(&path)
.and_then(Value::as_array_mut)
.ok_or("Destination rows missing.")?;
let index = rows.iter().position(|row| match &slot.1 {
FacetSelector::Annotation(_) => {
serde_json::json!({"path":row["path"],"field":row["field"]}) == key
}
FacetSelector::Binding(_) => row["referenced"] == key,
_ => false,
});
match (index, supplied.is_null()) {
(Some(index), false) => rows[index] = supplied,
(Some(index), true) => {
rows.remove(index);
}
(None, false) => rows.push(supplied),
(None, true) => {}
}
} else if keyed::supported(&slot.1) {
keyed::project_presence(
&mut value,
&slot.1,
Layer::Sa,
supplied.as_bool().ok_or("Expected typed presence.")?,
)?;
} else if let FacetSelector::Order(which) = slot.1 {
let order: Vec<Id> = serde_json::from_value(supplied).map_err(|error| error.to_string())?;
let current = ordered::read(&value, which)?;
if order.len() != order.iter().collect::<BTreeSet<_>>().len()
|| order.iter().collect::<BTreeSet<_>>() != current.iter().collect::<BTreeSet<_>>()
{
return Err("Combined ordered membership needs an explicit final target order containing each surviving member once. Review membership explicitly in the order's row.".into());
}
let path = keyed::path(&value, keyed::Family::Member(ordered::collection(which)))
.ok_or("Order collection missing.")?;
*value.pointer_mut(&path).expect("Closed collection path") =
serde_json::to_value(order).expect("Order");
} else {
let path = scalar::binding(project, slot.0, &slot.1)
.or_else(|| context_leaf_path(project, slot).map(str::to_owned))
.ok_or("A selected property was retired by another row's constructor proposal.")?;
crate::forms::set_shape_value(&mut value, &path, supplied)?;
}
install(project, slot.0, value)
}
pub(super) fn request(state: &State) -> Result<SemanticTransitionRequest, String> {
let base = &state.base;
let addresses = state.included_addresses();
if addresses.is_empty() {
return Err("Include at least one existing trace row in the combined review.".into());
}
let references = state.canonical_references()?;
let mut transition = TransitionRequest::new(
state.direction,
addresses.iter().map(|address| address.source).collect(),
);
transition.mappings = addresses
.iter()
.map(|address| TransitionMapping {
source: address.source,
target: Some(address.target),
target_kind: None,
})
.collect();
let mut request = SemanticTransitionRequest::new(transition);
let mut omissions = BTreeMap::new();
for address in &addresses {
if let Some(raw) = state
.row(*address)
.and_then(|row| row.operation_omission.as_ref())
&& omissions
.insert(address.source, raw.clone())
.is_some_and(|prior| prior != *raw)
{
return Err("Rows reviewing the same operation disagree on the explicit omission rationale. Retain one consistent decision before Apply.".into());
}
}
request.transition.operation_omissions = omissions
.into_iter()
.map(|(source, rationale)| OperationOmission { source, rationale })
.collect();
request.references = references.clone();
request.accepted_impacts = state.accepted_impacts.clone();
let mut slots = BTreeMap::new();
let mut proposals = vec![];
let mut candidate_targets = BTreeSet::new();
let mut implicit_orders = vec![];
let mut artifact_creations = BTreeMap::new();
for address in addresses {
let row = state
.row(address)
.ok_or("Included row draft is unavailable.")?;
let trace = row
.effective_trace(base)
.ok_or("Included trace is unavailable.")?;
request.traces.insert(address.source, address.trace);
request.reviews.push(SemanticReviewRequest {
source: address.source,
resolutions: row
.decisions
.iter()
.map(|(facet, decision)| FacetResolution {
facet: facet.clone(),
decision: *decision,
})
.collect(),
reset: row.resets.iter().cloned().collect(),
});
if row.decisions.iter().any(|(facet, decision)| {
!attachments::supported(facet)
&& !artifacts::supported(facet)
&& *decision == SemanticDecision::Candidate
}) {
candidate_targets.insert(address.target);
}
let final_value = row.combined_value(base, false).map_err(|error| {
format!(
"{} [{}]: {error}",
base.named(address.source),
base.named(address.trace)
)
})?;
let mut proposed = (**base).clone();
install(&mut proposed, address.target, final_value.clone())?;
let artifact =
row.artifacts
.propose(base, &trace, state.direction, &references, &row.decisions)?;
for (slot, observation, origin) in artifact.slots {
agree(state, &mut slots, slot, observation, address, &origin)?;
}
for seed in artifact.creations {
if artifact_creations
.insert(seed.id, seed.clone())
.is_some_and(|prior| prior != seed)
{
return Err(
"Independent artifact creation records disagree. All drafts are retained."
.into(),
);
}
}
candidate_targets.extend(artifact.candidates);
let mut groups = vec![];
let mut copies = vec![];
for (facet, decision) in &row.decisions {
if artifacts::supported(facet) {
continue;
}
if attachments::supported(facet) {
let attachment = row.attachments.propose(
base,
&trace,
state.direction,
facet,
*decision,
false,
)?;
let mut record = record_value(&proposed, attachment.slot.0)?;
*record
.pointer_mut(&attachment.path)
.ok_or("Attachment field was retired by another selected decision.")? =
attachment
.record
.pointer(&attachment.path)
.cloned()
.ok_or("Attachment proposal is incomplete.")?;
install(&mut proposed, attachment.slot.0, record)?;
let observed = observe(&proposed, &attachment.slot)?;
agree(
state,
&mut slots,
attachment.slot.clone(),
observed,
address,
facet,
)?;
if *decision == SemanticDecision::Candidate {
candidate_targets.insert(attachment.slot.0);
}
continue;
}
let Some(selector) = canonical(facet, state.direction.target_layer()) else {
continue;
};
let slot = (address.target, selector);
let observed = if *decision == SemanticDecision::KeepTarget {
observe(base, &slot)?
} else {
observe(&proposed, &slot)?
};
agree(state, &mut slots, slot.clone(), observed, address, facet)?;
if matches!(
facet,
FacetSelector::Relation(
RelationFacet::ContextSource | RelationFacet::ContextTarget
)
) {
let leaf = (
address.target,
FacetSelector::Relation(
if matches!(facet, FacetSelector::Relation(RelationFacet::ContextSource)) {
RelationFacet::Source
} else {
RelationFacet::Target
},
),
);
let observed = observe(
if *decision == SemanticDecision::KeepTarget {
base
} else {
&proposed
},
&leaf,
)?;
agree(state, &mut slots, leaf, observed, address, facet)?;
}
let first_equal = row.can_acknowledge_constructor(base, facet)
&& (*decision != SemanticDecision::Candidate
|| observe(base, &slot)? == observe(&proposed, &slot)?);
if scalar::grouped(facet) && (*decision == SemanticDecision::Candidate || !first_equal)
{
copies.push(facet.clone());
}
if scalar::grouped(facet) && first_equal && *decision == SemanticDecision::Candidate {
// Preserve authored payload changes even when the first equal-kind
// decision acknowledges only its discriminator. The core must see
// these changes and require separate descendant decisions.
let mut catalog = semantic_facets(base, &trace, &references)?;
catalog.extend(semantic_facets(&proposed, &trace, &references)?);
for fact in catalog {
if constructors::contains(facet, &fact.selector)
&& let Some(selector) =
canonical(&fact.selector, state.direction.target_layer())
{
let slot = (address.target, selector);
let supplied = observe(&proposed, &slot)?;
if supplied != observe(base, &slot)? {
agree(state, &mut slots, slot, supplied, address, facet)?;
}
}
}
}
if scalar::grouped(facet) && !first_equal {
groups.push(facet.clone());
let own = if *decision == SemanticDecision::KeepTarget {
&**base
} else {
&proposed
};
let mut catalog = semantic_facets(base, &trace, &references)?;
catalog.extend(semantic_facets(own, &trace, &references)?);
for fact in catalog {
if constructors::contains(facet, &fact.selector)
&& let Some(selector) =
canonical(&fact.selector, state.direction.target_layer())
{
let slot = (address.target, selector);
agree(
state,
&mut slots,
slot.clone(),
observe(own, &slot)?,
address,
facet,
)?;
}
}
}
}
for which in [
OrderedCollection::Steps,
OrderedCollection::FragmentBranches,
] {
if !row.decisions.contains_key(&FacetSelector::Order(which))
&& let Ok(order) = ordered::read(&record_value(&proposed, address.target)?, which)
&& ordered::read(&record_value(base, address.target)?, which)
.ok()
.as_ref()
!= Some(&order)
{
implicit_orders.push((address.target, which, order));
}
}
proposals.push(Proposal {
address,
value: final_value,
groups,
copies,
});
}
// Recheck every selected slot against every complete constructor scope, including
// new keys that were absent from that constructor row's own catalog.
let selected: Vec<_> = slots.keys().cloned().collect();
for proposal in &proposals {
if proposal.groups.is_empty() {
continue;
}
let mut proposed = (**base).clone();
install(
&mut proposed,
proposal.address.target,
proposal.value.clone(),
)?;
let row = state.row(proposal.address).expect("Included row");
for group in &proposal.groups {
let own = if row.decisions[group] == SemanticDecision::KeepTarget {
&**base
} else {
&proposed
};
for slot in &selected {
if slot.0 == proposal.address.target && constructors::contains(group, &slot.1) {
agree(
state,
&mut slots,
slot.clone(),
observe(own, slot)?,
proposal.address,
group,
)?;
}
}
}
}
let mut merged = (**base).clone();
merged.artifact_links.extend(artifact_creations);
let mut groups: Vec<_> = proposals
.iter()
.flat_map(|proposal| proposal.copies.iter().map(move |group| (proposal, group)))
.collect();
groups.sort_by_key(|(proposal, group)| {
(
proposal.address.target,
matches!(
group,
FacetSelector::Occurrence(OccurrenceFacet::MessageKind)
),
)
});
for (proposal, group) in groups {
let row = state.row(proposal.address).expect("Included row");
if row.decisions[group] == SemanticDecision::KeepTarget {
continue;
}
let mut value = record_value(&merged, proposal.address.target)?;
let supplied = &proposal.value;
for path in constructors::roots(base, proposal.address.target, group)
.ok_or("Constructor scope is unavailable.")?
{
*value
.pointer_mut(&path)
.ok_or("Overlapping constructors retire an authored scope.")? = supplied
.pointer(&path)
.cloned()
.ok_or("Constructor candidate is incomplete.")?;
}
install(&mut merged, proposal.address.target, value)?;
}
let mut ordered_slots: Vec<_> = slots.iter().collect();
ordered_slots.sort_by_key(|((_, facet), _)| matches!(facet, FacetSelector::Order(_)));
for (slot, intention) in ordered_slots {
write_slot(&mut merged, slot, &intention.value)?;
}
let mut final_orders = BTreeMap::new();
for (target, which, mut order) in implicit_orders {
if slots.contains_key(&(target, FacetSelector::Order(which))) {
continue;
}
let surviving: BTreeSet<_> = ordered::read(&record_value(&merged, target)?, which)?
.into_iter()
.collect();
order.retain(|id| surviving.contains(id));
if order.iter().copied().collect::<BTreeSet<_>>() != surviving {
return Err("Combined ordered membership needs an explicit final target order. Retained row drafts are unchanged.".into());
}
if final_orders
.insert((target, which), order.clone())
.is_some_and(|previous| previous != order)
{
return Err("Contradictory anchored orders need an explicit final target order. Retained row drafts are unchanged.".into());
}
}
for ((target, which), order) in final_orders {
write_slot(
&mut merged,
&(target, FacetSelector::Order(which)),
&serde_json::json!({"Value":order}),
)?;
}
for (slot, intention) in &slots {
if observe(&merged, slot)? != intention.value {
return Err(format!(
"Combined candidate does not preserve the selected intent for {} · {}.",
base.named(slot.0),
view::title(base, &slot.1)
));
}
}
for target in candidate_targets {
request
.candidates
.push(semantic_record(&merged, target).ok_or("Merged candidate target disappeared.")?);
}
Ok(request)
}