Complete source 6 engineering workflows and preserve release validation evidence
This commit is contained in:
@@ -0,0 +1,626 @@
|
||||
//! 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);
|
||||
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((_, 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)
|
||||
.ok_or("A selected property was retired by another row's constructor proposal.")?;
|
||||
*value
|
||||
.pointer_mut(&path)
|
||||
.ok_or("Selected property is no longer available.")? = 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);
|
||||
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)?;
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user