Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
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.
689 lines
26 KiB
Rust
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)
|
|
}
|