Improve behavior authoring and typed trace history (untested checkpoint)
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s

This commit is contained in:
Codex
2026-09-10 11:07:07 -07:00
parent 1797516f46
commit 01bb52a76d
25 changed files with 4078 additions and 171 deletions
@@ -0,0 +1,666 @@
//! Typed editing of stored observations, independently of the live counterparts.
//! The checked-in, contract-tested OpenAPI descriptor supplies only field shapes.
//! Serde and core historical validation remain the admission authority.
use super::*;
use iced::widget::column;
use std::sync::OnceLock;
pub(super) const TOGGLE: &str = "/$stored_history";
pub(super) const PAGE: &str = "/$stored_history_page";
pub(super) const VARIANT: &str = "/$stored_history_variant";
const ROOT: &str = "/baseline";
const FACETS: &str = "/baseline/semantic/facets";
const PAGE_SIZE: usize = 12;
const MAX_DEPTH: usize = 32;
#[derive(Default)]
pub(super) struct State {
pub open: bool,
pub pages: BTreeMap<String, usize>,
}
pub(super) fn is_path(path: &str) -> bool {
path == ROOT || path.starts_with("/baseline/")
}
fn api() -> &'static Value {
static API: OnceLock<Value> = OnceLock::new();
API.get_or_init(|| {
serde_json::from_str(include_str!("../../../../docs/openapi.json"))
.expect("The bundled OpenAPI descriptor is valid JSON")
})
}
fn resolve(mut schema: &'static Value) -> Result<&'static Value, &'static str> {
for _ in 0..MAX_DEPTH {
let Some(reference) = schema.get("$ref").and_then(Value::as_str) else {
return Ok(schema);
};
schema = reference
.strip_prefix('#')
.and_then(|path| api().pointer(path))
.ok_or("Stored-history descriptor has an unresolved local reference.")?;
}
Err("Stored-history descriptor reference depth exceeded.")
}
fn named(name: &str) -> Result<&'static Value, &'static str> {
api()["components"]["schemas"]
.get(name)
.ok_or("Stored-history descriptor is missing a required type.")
}
fn alternatives(schema: &'static Value) -> Option<&'static Vec<Value>> {
schema
.get("oneOf")
.or_else(|| schema.get("anyOf"))
.and_then(Value::as_array)
}
fn at_schema(schema: &'static Value, parts: &[&str], depth: usize) -> Option<&'static Value> {
if depth > MAX_DEPTH {
return None;
}
let schema = resolve(schema).ok()?;
let Some((part, rest)) = parts.split_first() else {
return Some(schema);
};
if let Some(options) = alternatives(schema) {
return options.iter().find_map(|s| at_schema(s, parts, depth + 1));
}
match schema.get("type").and_then(Value::as_str) {
Some("object") => at_schema(schema.get("properties")?.get(*part)?, rest, depth + 1),
Some("array") if part.parse::<usize>().is_ok() => {
at_schema(schema.get("items")?, rest, depth + 1)
}
_ => None,
}
}
fn schema_at(path: &str) -> Option<&'static Value> {
let tail = path.strip_prefix(ROOT)?;
let parts: Vec<_> = tail
.trim_start_matches('/')
.split('/')
.filter(|s| !s.is_empty())
.collect();
at_schema(named("TraceBaseline").ok()?, &parts, 0)
}
pub(super) fn has_field(path: &str) -> bool {
schema_at(path).is_some()
}
pub(super) fn free_string(path: &str) -> bool {
fn text_shape(schema: &'static 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(|s| text_shape(s, depth + 1));
}
schema.get("type").and_then(Value::as_str) == Some("string")
&& schema.get("enum").is_none()
&& schema.get("format").and_then(Value::as_str) != Some("uuid")
}
schema_at(path).is_some_and(|s| text_shape(s, 0))
}
/// A shape seed is a draft, never an acknowledged historical fact. UUIDs start
/// empty. Observed starts NA on both sides, which core rejects. Typed defaults
/// inside Value are introduced only after the engineer chooses that branch.
fn seed(schema: &'static Value, depth: usize) -> Result<Value, &'static str> {
if depth > MAX_DEPTH {
return Err("Stored-history constructor depth exceeded.");
}
let schema = resolve(schema)?;
if let Some(options) = alternatives(schema) {
if options
.iter()
.any(|s| matches_value(s, &json!("NotApplicable"), 0))
{
return Ok(json!("NotApplicable"));
}
if options.iter().any(|s| matches_value(s, &Value::Null, 0)) {
return Ok(Value::Null);
}
return seed(
options
.first()
.ok_or("Stored-history union has no constructors.")?,
depth + 1,
);
}
if let Some(values) = schema.get("enum").and_then(Value::as_array) {
return values
.first()
.cloned()
.ok_or("Stored-history enumeration has no values.");
}
Ok(match schema.get("type").and_then(Value::as_str) {
Some("object") => {
let fields = schema
.get("properties")
.and_then(Value::as_object)
.ok_or("Stored-history object has no closed field descriptor.")?;
let mut result = serde_json::Map::new();
for (name, schema) in fields {
result.insert(name.clone(), seed(schema, depth + 1)?);
}
Value::Object(result)
}
Some("array") => json!([]),
Some("boolean") => json!(false),
Some("integer") => json!(0),
Some("number") => json!(0.0),
Some("string") => json!(""),
Some("null") => Value::Null,
_ => return Err("Unsupported stored-history field descriptor."),
})
}
/// Shape matching selects the displayed constructor; it is not validation.
/// Required values may be incomplete while their chosen branch stays editable.
fn matches_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(|s| matches_value(s, value, depth + 1));
}
if let Some(choices) = schema.get("enum").and_then(Value::as_array) {
return choices.contains(value);
}
match schema.get("type").and_then(Value::as_str) {
Some("object") => value.as_object().is_some_and(|fields| {
schema
.get("properties")
.and_then(Value::as_object)
.is_some_and(|properties| {
fields.keys().all(|key| properties.contains_key(key))
&& schema
.get("required")
.and_then(Value::as_array)
.is_none_or(|required| {
required
.iter()
.filter_map(Value::as_str)
.all(|key| fields.contains_key(key))
})
})
}),
Some("array") => value.is_array(),
Some("boolean") => value.is_boolean(),
Some("integer" | "number") => value.is_number(),
Some("string") => value.is_string(),
Some("null") => value.is_null(),
_ => false,
}
}
fn label(name: &str) -> String {
match name {
"oa" => return "Operational observation".into(),
"sa" => return "System observation".into(),
"NotApplicable" => return "Not applicable".into(),
"Value" => return "Observed value".into(),
"keys" => return "Exact historical keys".into(),
"review" => return "Stored observations".into(),
_ => {}
}
let mut out = String::new();
let mut previous_lower = false;
for c in name.chars() {
if c == '_' {
out.push(' ');
} else {
if c.is_uppercase() && previous_lower {
out.push(' ');
}
out.push(c);
}
previous_lower = c.is_lowercase();
}
let mut chars = out.chars();
chars
.next()
.map(|c| c.to_uppercase().collect::<String>() + chars.as_str())
.unwrap_or_default()
}
fn branch_label(schema: &'static Value) -> String {
let Ok(schema) = resolve(schema) else {
return "Unavailable type".into();
};
if let Some(word) = schema
.get("enum")
.and_then(Value::as_array)
.filter(|v| v.len() == 1)
.and_then(|v| v[0].as_str())
{
return label(word);
}
if schema.get("enum").is_some() {
return "Enumeration · choose below".into();
}
if let Some(fields) = schema.get("properties").and_then(Value::as_object)
&& fields.len() == 1
{
return label(fields.keys().next().unwrap());
}
match schema.get("type").and_then(Value::as_str) {
Some("null") => "Absent (none)".into(),
Some("object") => "Set record".into(),
Some("array") => "Set list".into(),
Some("string") if schema.get("format").and_then(Value::as_str) == Some("uuid") => {
"Set historical UUID".into()
}
Some("string") => "Set text".into(),
Some("integer" | "number") => "Set number".into(),
_ => "Set value / choose constructor".into(),
}
}
#[derive(Clone, PartialEq, Eq)]
struct FactChoice {
label: String,
value: Value,
}
impl std::fmt::Display for FactChoice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.label)
}
}
/// Flatten only the closed FacetReview selector levels, not observation values.
/// Collection and incident-end choices are selectors too, so none is guessed.
fn fact_choices() -> Result<&'static Vec<FactChoice>, &'static str> {
static CHOICES: OnceLock<Result<Vec<FactChoice>, &'static str>> = OnceLock::new();
CHOICES
.get_or_init(|| {
let root = resolve(named("FacetReview")?)?;
let mut result = vec![];
for branch in alternatives(root).ok_or("FacetReview must be a closed union.")? {
let outer = resolve(branch)?;
let (family, payload) = outer["properties"]
.as_object()
.and_then(|v| v.iter().next())
.ok_or("Facet family is missing.")?;
let payload = resolve(payload)?;
if let Some(leaves) = alternatives(payload) {
for leaf in leaves {
let mut value = serde_json::Map::new();
value.insert(family.clone(), seed(leaf, 0)?);
result.push(FactChoice {
label: format!("{} / {}", label(family), branch_label(leaf)),
value: Value::Object(value),
});
}
} else if family == "Artifact" {
let leaves = alternatives(resolve(&payload["properties"]["review"])?)
.ok_or("Artifact facts must be a closed union.")?;
for leaf in leaves {
let mut value = seed(branch, 0)?;
value[family]["review"] = seed(leaf, 0)?;
result.push(FactChoice {
label: format!("Artifact / {}", branch_label(leaf)),
value,
});
}
} else if matches!(family.as_str(), "Member" | "Order" | "Incident") {
let field = if family == "Incident" {
"end"
} else {
"collection"
};
let choices = resolve(&payload["properties"][field])?["enum"]
.as_array()
.ok_or("Historical selector choices are missing.")?;
for choice in choices {
let mut value = seed(branch, 0)?;
value[family][field] = choice.clone();
result.push(FactChoice {
label: format!(
"{} / {}",
label(family),
label(choice.as_str().unwrap_or(""))
),
value,
});
}
} else {
result.push(FactChoice {
label: label(family),
value: seed(branch, 0)?,
});
}
}
Ok(result)
})
.as_ref()
.map_err(|error| *error)
}
fn variant_message(path: &str, value: Value, present: bool) -> Message {
Message::FieldShape(
if present {
format!("{VARIANT}{path}")
} else {
path.into()
},
value,
)
}
fn branch_seed(schema: &'static Value) -> Result<Value, &'static str> {
let schema = resolve(schema)?;
if let Some(values) = schema
.get("enum")
.and_then(Value::as_array)
.filter(|v| v.len() == 1)
{
Ok(values[0].clone())
} else {
seed(schema, 0)
}
}
fn branch_has_fields(schema: &'static Value) -> bool {
let Ok(schema) = resolve(schema) else {
return false;
};
schema.get("type").and_then(Value::as_str) != Some("null")
&& schema
.get("enum")
.and_then(Value::as_array)
.is_none_or(|values| values.len() > 1)
}
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
.history
.pages
.get(path)
.copied()
.unwrap_or(0)
.min(last);
let event = |page| Message::FieldShape(format!("{PAGE}{path}"), json!(page));
let view = row![
button("Previous").on_press_maybe((page > 0).then(|| event(page - 1))),
text(format!(
"{}–{} of {count}",
if count == 0 { 0 } else { page * PAGE_SIZE + 1 },
((page + 1) * PAGE_SIZE).min(count)
))
.size(11),
button("Next").on_press_maybe((page < last).then(|| event(page + 1)))
]
.spacing(6)
.align_y(iced::Center)
.into();
(page, view)
}
fn render<'a>(
draft: &'a Draft,
project: &'a Project,
schema: &'static Value,
value: Option<&'a Value>,
path: &str,
title: &str,
depth: usize,
) -> View<'a, Message> {
if depth > MAX_DEPTH {
return text("Stored-history field nesting exceeds the supported editor depth.")
.size(11)
.into();
}
let schema = match resolve(schema) {
Ok(s) => s,
Err(e) => return text(e).size(11).into(),
};
let present = value.is_some();
let value = value.unwrap_or(&Value::Null);
if let Some(options) = alternatives(schema) {
let initial: Vec<_> = match options.iter().map(branch_seed).collect::<Result<_, _>>() {
Ok(values) => values,
Err(error) => {
return text(format!(
"Stored-history constructor is unavailable: {error}"
))
.size(11)
.into();
}
};
let labels: Vec<_> = options.iter().map(branch_label).collect();
let selected = options.iter().position(|s| matches_value(s, value, 0));
let path_owned = path.to_string();
let labels_owned = labels.clone();
let mut out = column![
text(title.to_owned()).size(11).color(style::MUTED),
pick_list(
labels,
selected.map(|i| branch_label(&options[i])),
move |choice| {
let index = labels_owned.iter().position(|v| v == &choice).unwrap();
variant_message(&path_owned, initial[index].clone(), present)
}
)
.placeholder("Choose stored value / constructor…")
.text_size(12)
.padding(8)
.width(Fill)
]
.spacing(6);
if let Some(index) = selected {
let branch = resolve(&options[index]).unwrap_or(&options[index]);
if branch_has_fields(branch) {
out = out.push(render(
draft,
project,
branch,
Some(value),
path,
"",
depth + 1,
));
}
}
return out.into();
}
if let Some(words) = schema.get("enum").and_then(Value::as_array) {
let choices: Vec<_> = words
.iter()
.filter_map(Value::as_str)
.map(str::to_owned)
.collect();
let selected = value
.as_str()
.map(str::to_owned)
.filter(|v| choices.contains(v));
let path = path.to_string();
return column![
text(title.to_owned()).size(11).color(style::MUTED),
pick_list(choices, selected, move |word| Message::FieldShape(
path.clone(),
json!(word)
))
.placeholder("Choose an explicit value…")
.text_size(12)
.padding(8)
.width(Fill)
]
.spacing(6)
.into();
}
match schema.get("type").and_then(Value::as_str) {
Some("object") => {
let Some(fields) = schema.get("properties").and_then(Value::as_object) else {
return text("Unsupported stored-history object shape.").size(11).into();
};
let mut out = column![].spacing(8);
if !title.is_empty() { out = out.push(text(title.to_owned()).size(12).color(style::INK)); }
for (key, child_schema) in fields {
out = out.push(render(draft, project, child_schema, value.get(key), &format!("{path}/{key}"), &label(key), depth + 1));
}
out.into()
}
Some("array") => {
let Some(items) = value.as_array() else {
return button("Initialize empty list").on_press(Message::FieldShape(path.into(), json!([]))).into();
};
let Some(item_schema) = schema.get("items") else { return text("Missing history list item type.").into(); };
let mut out = column![text(title.to_owned()).size(12)].spacing(8);
if path == FACETS {
match fact_choices() {
Ok(choices) => {
let path = path.to_owned();
out = out.push(pick_list(choices.clone(), None::<FactChoice>, move |choice| Message::FieldAppend(path.clone(), choice.value))
.placeholder("Add: choose family and fact…").text_size(12).padding(8).width(Fill));
}
Err(error) => { out = out.push(text(error).size(11)); }
}
} else {
match seed(item_schema, 0) {
Ok(initial) => out = out.push(button("Add list value").on_press(Message::FieldAppend(path.into(), initial))),
Err(error) => out = out.push(text(error).size(11)),
}
}
let (page, pager) = paging(draft, path, items.len());
if items.len() > PAGE_SIZE { out = out.push(pager); }
for (index, item) in items.iter().enumerate().skip(page * PAGE_SIZE).take(PAGE_SIZE) {
let item_path = format!("{path}/{index}");
let mut card = column![render(draft, project, item_schema, Some(item), &item_path, &format!("{} {}", title, index + 1), depth + 1)].spacing(8);
if path == FACETS {
if let Some(current) = current_observation(draft, project, &item_path) {
card = card.push(button("Use current counterpart observations")
.on_press(Message::FieldShape(item_path.clone(), json!(current))).style(style::nav));
}
}
card = card.push(row![
button("↑").on_press_maybe((index > 0).then(|| Message::FieldArray(path.into(), Some(index), -1))),
button("↓").on_press_maybe((index + 1 < items.len()).then(|| Message::FieldArray(path.into(), Some(index), 1))),
button("Remove").on_press(Message::FieldArray(path.into(), Some(index), 0))
].spacing(6));
out = out.push(container(card).padding(10).style(style::subtle));
}
if items.is_empty() { out = out.push(text("Empty list · no stored entries").size(11).color(style::MUTED)); }
out.into()
}
Some("boolean") => {
let path = path.to_owned();
checkbox(value.as_bool().unwrap_or(false)).label(title.to_owned())
.on_toggle(move |v| Message::FieldShape(path.clone(), json!(v))).text_size(12).into()
}
Some("integer" | "number" | "string") => {
let is_uuid = schema.get("format").and_then(Value::as_str) == Some("uuid");
let numeric = schema.get("type").and_then(Value::as_str) != Some("string");
let mut out = column![text(title.to_owned()).size(11).color(style::MUTED)].spacing(5);
if let Some(input) = draft.inputs.get(path) {
let owned = path.to_owned();
if !numeric && !is_uuid && !draft.exact_strings {
out = out.push(text_editor(&input.editor).on_action(move |a| Message::FieldText(owned.clone(), a)).size(12).height(72).padding(8));
} else {
out = out.push(text_input(if is_uuid { "Historical UUID, including deleted identities" } else { "Stored value" }, &input.raw)
.on_input(move |raw| Message::Field(owned.clone(), raw)).size(12).padding(8));
}
} else {
match seed(schema, 0) {
Ok(value) => out = out.push(button("Set stored value").on_press(Message::FieldShape(path.into(), value))),
Err(error) => out = out.push(text(error).size(11)),
}
}
if is_uuid { out = out.push(text("Historical identity · existence in the current model is not required.").size(10).color(style::MUTED)); }
out.into()
}
Some("null") => text("Absent (none)").size(11).into(),
_ => text("Unsupported stored-history descriptor. The draft is retained; this field cannot be edited with these controls.").size(11).into(),
}
}
fn current_observation(draft: &Draft, project: &Project, item_path: &str) -> Option<FacetReview> {
let raw = draft
.materialize_scopes(&[item_path.to_owned(), "/oa".into(), "/sa".into()])
.ok()?;
let fact: FacetReview = serde_json::from_value(raw.pointer(item_path)?.clone()).ok()?;
let oa = raw.get("oa")?.as_str()?.parse().ok()?;
let sa = raw.get("sa")?.as_str()?.parse().ok()?;
semantic::capture_facet(project, oa, sa, &fact.selector()).ok()
}
pub(super) fn view<'a>(
draft: &'a Draft,
project: &'a Project,
value: &'a Value,
) -> View<'a, Message> {
let count = value
.pointer("/semantic/facets")
.and_then(Value::as_array)
.map_or(0, Vec::len);
let mut out = column![
text("Stored review history").size(12).color(style::INK),
text(if value.is_null() {
"No stored baseline".into()
} else {
format!("Prose baseline · {count} semantic observations")
})
.size(11)
.color(style::MUTED),
button(if draft.history.open {
"Close stored-history editor"
} else {
"Edit stored history (advanced)…"
})
.on_press(Message::FieldShape(
TOGGLE.into(),
json!(!draft.history.open)
))
.style(style::nav)
]
.spacing(8);
if !draft.history.open {
return out.into();
}
out = out.push(text("These edits change the trace's stored observations only. They do not change either counterpart. Ordinary Keep/Take review remains available from Traceability.").size(11));
if value.is_null() {
return match named("TraceBaseline").and_then(|s| seed(s, 0)) {
Ok(initial) => out
.push(
button("Start empty stored baseline")
.on_press(Message::FieldShape(ROOT.into(), initial)),
)
.into(),
Err(error) => out.push(text(error).size(11)).into(),
};
}
out = out.push(
button("Clear whole stored baseline")
.on_press(Message::FieldShape(ROOT.into(), Value::Null))
.style(style::nav),
);
match named("TraceBaseline") {
Ok(schema) => out = out.push(render(draft, project, schema, Some(value), ROOT, "", 0)),
Err(error) => out = out.push(text(error).size(11)),
}
let checked = draft.materialize_scopes(&[ROOT.into()]).and_then(|raw| {
let baseline: TraceBaseline =
serde_json::from_value(raw["baseline"].clone()).map_err(|e| e.to_string())?;
baseline.semantic.as_ref().map_or(Ok(()), |b| {
semantic::validate_semantic_baseline(b).map_err(|e| e.join("; "))
})
});
if let Err(error) = checked {
out = out.push(text(format!("Stored history is incomplete or invalid: {error}")).size(11));
}
out.into()
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,733 @@
use super::*;
use crate::document::Document;
fn fixture(baseline: Option<TraceBaseline>) -> (Project, Id) {
let mut project = Project::empty();
let oa = project
.add_element(Kind::Activity, Layer::Oa, "Assess need", None)
.unwrap();
let sa = project
.add_element(Kind::Function, Layer::Sa, "Assess request", None)
.unwrap();
let id = Id::from_u128(9001);
project.traces.insert(
id,
Trace {
id,
alias: "assessment_review".into(),
name: "Assessment correspondence".into(),
description: "Engineering review history".into(),
oa,
sa,
kind: TraceKind::Refinement,
baseline,
},
);
validate_candidate(&project).unwrap();
(project, id)
}
fn baseline(facets: Vec<FacetReview>) -> TraceBaseline {
TraceBaseline {
oa: TracedProperties {
name: "Earlier OA name".into(),
description: "OA note".into(),
},
sa: TracedProperties {
name: "Earlier SA name".into(),
description: "SA note".into(),
},
semantic: Some(SemanticBaseline { facets }),
}
}
fn document(project: &Project) -> Document {
let mut bundle = source_project::format_bundle(project);
bundle
.files
.get_mut(&bundle.entry)
.unwrap()
.insert_str(0, "// Retain this authored project note.\n\n");
Document::from_bundle(bundle, Default::default()).unwrap()
}
fn choice(selector: FacetSelector) -> Value {
fact_choices()
.unwrap()
.iter()
.find(|choice| {
serde_json::from_value::<FacetReview>(choice.value.clone())
.unwrap()
.selector()
== selector
})
.unwrap()
.value
.clone()
}
fn add_header(draft: &mut Draft) {
draft
.append(FACETS, choice(FacetSelector::Header(HeaderFacet::Name)))
.unwrap();
}
fn assert_history_transaction(document: &mut Document, draft: &Draft, expected: &Project) {
let before = document.project().clone();
let source_before = document.bundle().clone();
document.execute(draft.command().unwrap()).unwrap();
assert_eq!(document.project(), expected);
let accepted_source = document.bundle().clone();
assert!(
accepted_source.files[&accepted_source.entry]
.starts_with("// Retain this authored project note.\n\n")
);
assert_eq!(
Document::from_bundle(accepted_source.clone(), Default::default())
.unwrap()
.project(),
expected
);
assert_eq!(
serde_json::from_slice::<Project>(&serde_json::to_vec(expected).unwrap()).unwrap(),
*expected
);
assert!(document.undo());
assert_eq!(document.project(), &before);
assert_eq!(document.bundle(), &source_before);
assert!(
!document.undo(),
"one stored-history Apply must create one history entry"
);
assert!(document.redo());
assert_eq!(document.project(), expected);
assert_eq!(document.bundle(), &accepted_source);
assert!(!document.redo());
}
#[test]
fn history_absent_baseline_and_empty_history_use_explicit_facts_and_exact_document_history() {
for initial in [None, Some(baseline(vec![]))] {
let (project, id) = fixture(initial);
let mut doc = document(&project);
let mut draft = Draft::from_project(&project, id).unwrap();
assert!(!draft.history.open);
draft.shape(TOGGLE, json!(true)).unwrap();
assert!(draft.history.open);
assert!(
!draft.dirty(),
"opening the advanced editor cannot acknowledge history"
);
if draft.value["baseline"].is_null() {
draft
.shape(ROOT, seed(named("TraceBaseline").unwrap(), 0).unwrap())
.unwrap();
draft
.shape("/baseline/semantic", json!({"facets":[]}))
.unwrap();
}
let untouched = draft.value.clone();
assert!(
draft
.array(FACETS, None, 0)
.unwrap_err()
.contains("Choose a typed")
);
assert_eq!(
draft.value, untouched,
"the old nil-UUID Add must not mutate the draft"
);
add_header(&mut draft);
let before = doc.snapshot();
assert!(
doc.execute(draft.command().unwrap()).is_err(),
"NA/NA is incomplete, not a reviewed default"
);
assert!(std::sync::Arc::ptr_eq(&before, &doc.snapshot()));
let current = current_observation(&draft, &project, &format!("{FACETS}/0")).unwrap();
assert_eq!(
current,
semantic::capture_facet(
&project,
project.traces[&id].oa,
project.traces[&id].sa,
&FacetSelector::Header(HeaderFacet::Name)
)
.unwrap()
);
draft.shape(&format!("{FACETS}/0"), json!(current)).unwrap();
let mut expected = project.clone();
expected.traces.get_mut(&id).unwrap().baseline =
Some(serde_json::from_value(draft.materialize().unwrap()["baseline"].clone()).unwrap());
assert_history_transaction(&mut doc, &draft, &expected);
}
}
#[test]
fn history_missing_semantic_field_can_be_added_and_removed_without_touching_prose_or_live_objects()
{
let mut prose = baseline(vec![]);
prose.semantic = None;
let (project, id) = fixture(Some(prose));
let mut doc = document(&project);
let mut draft = Draft::from_project(&project, id).unwrap();
assert!(draft.value.pointer("/baseline/semantic").is_none());
draft
.shape("/baseline/semantic", json!({"facets":[]}))
.unwrap();
let mut expected = project.clone();
expected
.traces
.get_mut(&id)
.unwrap()
.baseline
.as_mut()
.unwrap()
.semantic = Some(SemanticBaseline::default());
assert_history_transaction(&mut doc, &draft, &expected);
let mut restored = Draft::from_project(&expected, id).unwrap();
restored.shape("/baseline/semantic", Value::Null).unwrap();
assert_eq!(
execute_batch(&expected, &[restored.command().unwrap()]).unwrap(),
project
);
}
/// Generate type witnesses independently of the editor's deliberately incomplete
/// seeds, then ask the real closed Rust types and historical validator to admit them.
fn witness(schema: &'static Value, active: &Value, depth: usize) -> Value {
assert!(
depth <= MAX_DEPTH,
"recursive/unsupported historical descriptor"
);
let schema = resolve(schema).unwrap();
if let Some(options) = alternatives(schema) {
let selected = if !active.is_null() && active != "NotApplicable" {
options.iter().find(|s| matches_value(s, active, 0))
} else {
None
};
let selected = selected
.or_else(|| {
options.iter().find(|s| {
!matches_value(s, &Value::Null, 0)
&& !matches_value(s, &json!("NotApplicable"), 0)
})
})
.unwrap_or(&options[0]);
return witness(selected, active, depth + 1);
}
if let Some(values) = schema.get("enum").and_then(Value::as_array) {
return if values.contains(active) {
active.clone()
} else {
values[0].clone()
};
}
match schema["type"].as_str().unwrap() {
"object" => schema["properties"]
.as_object()
.unwrap()
.iter()
.map(|(key, s)| {
(
key.clone(),
witness(s, active.get(key).unwrap_or(&Value::Null), depth + 1),
)
})
.collect::<serde_json::Map<_, _>>()
.into(),
"array" => json!([witness(&schema["items"], &Value::Null, depth + 1)]),
"string" if schema["format"] == "uuid" => json!(Id::from_u128(777)),
"string" => json!("Earlier Ω value\nwith provenance"),
"integer" => json!(1),
"number" => json!(1.25),
"boolean" => json!(true),
"null" => Value::Null,
other => panic!("unsupported historical schema {other}"),
}
}
#[test]
fn history_every_descriptor_fact_choice_matches_actual_closed_types_and_source_contract() {
let (project, id) = fixture(Some(baseline(vec![])));
let mut selectors = BTreeSet::new();
let choices = fact_choices().unwrap();
assert!(
choices.len() > 100,
"the full scalar and keyed catalog must be reachable"
);
for entry in choices {
let incomplete: FacetReview = serde_json::from_value(entry.value.clone()).unwrap();
assert!(
selectors.insert(incomplete.selector()),
"duplicate Add selector: {}",
entry.label
);
assert!(
semantic::validate_semantic_baseline(&SemanticBaseline {
facets: vec![incomplete]
})
.is_err()
);
let complete = witness(named("FacetReview").unwrap(), &entry.value, 0);
let typed: FacetReview = serde_json::from_value(complete.clone())
.unwrap_or_else(|e| panic!("{}: {e}", entry.label));
semantic::validate_semantic_baseline(&SemanticBaseline {
facets: vec![typed.clone()],
})
.unwrap_or_else(|e| panic!("{}: {e:?}", entry.label));
let mut draft = Draft::from_project(&project, id).unwrap();
draft.append(FACETS, complete).unwrap();
draft.history.open = true;
let _controls = view(&draft, &project, &draft.value["baseline"]);
let result = execute_batch(&project, &[draft.command().unwrap()]).unwrap();
let parsed = source_project::compile_bundle(
&source_project::format_bundle(&result),
Default::default(),
)
.unwrap();
assert_eq!(
parsed.decoded.project, result,
"source parity for {}",
entry.label
);
assert_eq!(
result.traces[&id]
.baseline
.as_ref()
.unwrap()
.semantic
.as_ref()
.unwrap()
.facets,
vec![typed]
);
}
}
#[test]
fn history_deleted_keys_and_observation_uuids_remain_exact_and_duplicate_history_rejects() {
let old_link = Id::from_u128(401);
let old_owner = Id::from_u128(402);
let fact = FacetReview::Artifact {
keys: KeyPair {
oa: Some(old_link),
sa: None,
},
review: ArtifactReview::Owner(Reviewed {
oa: Observed::Value(old_owner),
sa: Observed::NotApplicable,
}),
};
let (project, id) = fixture(Some(baseline(vec![fact])));
assert!(!project.contains(old_link) && !project.contains(old_owner));
let mut doc = document(&project);
let mut draft = Draft::from_project(&project, id).unwrap();
let key_path = format!("{FACETS}/0/Artifact/keys/oa");
let owner_path = format!("{FACETS}/0/Artifact/review/Owner/oa/Value");
assert!(!free_string(&key_path) && !free_string(&owner_path));
let edited_key = Id::from_u128(403);
let edited_owner = Id::from_u128(404);
draft.input(&key_path, edited_key.to_string());
draft.input(&owner_path, "bad uuid".into());
assert!(draft.command().is_err());
draft.input(&owner_path, edited_owner.to_string());
let accepted = draft
.materialize()
.unwrap()
.pointer(&format!("{FACETS}/0"))
.unwrap()
.clone();
draft.append(FACETS, accepted.clone()).unwrap();
let before = doc.snapshot();
assert!(
doc.execute(draft.command().unwrap()).is_err(),
"overlapping historic addresses must reject"
);
assert!(std::sync::Arc::ptr_eq(&before, &doc.snapshot()));
draft.array(FACETS, Some(1), 0).unwrap();
let mut expected = project.clone();
expected
.traces
.get_mut(&id)
.unwrap()
.baseline
.as_mut()
.unwrap()
.semantic
.as_mut()
.unwrap()
.facets = vec![serde_json::from_value(accepted).unwrap()];
assert_history_transaction(&mut doc, &draft, &expected);
}
#[test]
fn history_invalid_numbers_survive_constructor_switch_reorder_removal_and_recovery() {
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");
draft
.shape(&format!("{VARIANT}{side}"), json!({"Value": 7}))
.unwrap();
draft.input(&format!("{side}/Value"), "-".into());
add_header(&mut draft);
assert_eq!(draft.inputs[&format!("{side}/Value")].raw, "-");
draft
.shape(&format!("{VARIANT}{side}"), json!("NotApplicable"))
.unwrap();
draft.array(FACETS, Some(0), 1).unwrap();
let moved_side = format!("{FACETS}/1/Data/IntegerMinimum/oa");
draft
.shape(&format!("{VARIANT}{moved_side}"), json!({"Value": 0}))
.unwrap();
assert_eq!(
draft.inputs[&format!("{moved_side}/Value")].raw,
"-",
"inactive constructor drafts follow the historical row"
);
let saved = draft.recovery_snapshot();
let mut recovered = Draft::from_recovery(&project, saved).unwrap();
assert!(recovered.history.open);
assert_eq!(recovered.inputs[&format!("{moved_side}/Value")].raw, "-");
assert!(recovered.command().is_err());
recovered.array(FACETS, Some(0), 0).unwrap();
assert_eq!(recovered.inputs[&format!("{side}/Value")].raw, "-");
recovered.input(&format!("{side}/Value"), i64::MIN.to_string());
let mut doc = document(&project);
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(i64::MIN)),
sa: Observed::NotApplicable,
}))],
});
assert_history_transaction(&mut doc, &recovered, &expected);
}
#[test]
fn history_compound_value_and_constructor_variants_are_editable_without_live_context() {
let (project, id) = fixture(Some(baseline(vec![])));
let old = Id::from_u128(700);
let values = vec![
FacetReview::Data(DataReview::Default(Reviewed {
oa: Observed::Value(Some(DataValue::Boolean(true))),
sa: Observed::NotApplicable,
})),
FacetReview::Field(FieldReview::Type(Reviewed {
oa: Observed::Value(TypeRef::Named(old)),
sa: Observed::Value(TypeRef::Real),
})),
FacetReview::Occurrence(OccurrenceReview::Operation(Reviewed {
oa: Observed::NotApplicable,
sa: Observed::Value(Some(OperationUse {
allocation: Some(old),
})),
})),
FacetReview::Relation(RelationReview::ContextSource(Reviewed {
oa: Observed::Value(OccurrenceEndpoint {
references: vec![old, Id::from_u128(701)],
occurrence: Id::from_u128(702),
}),
sa: Observed::NotApplicable,
})),
FacetReview::Relation(RelationReview::ContextPurpose(Reviewed {
oa: Observed::Value(ContextualLinkPurpose::CommonWork { work: old }),
sa: Observed::NotApplicable,
})),
FacetReview::Annotation {
keys: KeyPair {
oa: Some(AnnotationKey {
path: vec![old],
field: old,
}),
sa: None,
},
review: Reviewed {
oa: Observed::Value(Some(MessageValue {
path: vec![old],
field: old,
expression: "verbatim\tΩ".into(),
})),
sa: Observed::NotApplicable,
},
},
FacetReview::Binding {
keys: KeyPair {
oa: Some(old),
sa: None,
},
review: Reviewed {
oa: Observed::Value(Some(LifelineBinding {
referenced: old,
local: Id::from_u128(702),
})),
sa: Observed::NotApplicable,
},
},
];
let mut draft = Draft::from_project(&project, id).unwrap();
for value in &values {
draft.append(FACETS, json!(value)).unwrap();
}
let literal = format!("{FACETS}/0/Data/Default/oa/Value");
for value in [
DataValue::Integer(i64::MAX),
DataValue::Real(-0.25),
DataValue::String("quoted\r\nΩ".into()),
DataValue::Enumeration("Literal".into()),
] {
draft
.shape(&format!("{VARIANT}{literal}"), json!(value))
.unwrap();
let model = execute_batch(&project, &[draft.command().unwrap()]).unwrap();
assert!(
matches!(&model.traces[&id].baseline.as_ref().unwrap().semantic.as_ref().unwrap().facets[0], FacetReview::Data(DataReview::Default(Reviewed { oa: Observed::Value(Some(actual)), .. })) if actual == &value)
);
}
let purpose = format!("{FACETS}/4/Relation/ContextPurpose/oa/Value");
draft
.shape(
&format!("{VARIANT}{purpose}"),
json!({"Precedence":{"guard":"reviewed before dispatch"}}),
)
.unwrap();
let mut doc = document(&project);
let mut expected = project.clone();
expected.traces.get_mut(&id).unwrap().baseline =
Some(serde_json::from_value(draft.materialize().unwrap()["baseline"].clone()).unwrap());
assert_history_transaction(&mut doc, &draft, &expected);
}
#[test]
fn history_exact_strings_do_not_confuse_uuid_shaped_prose_or_literal_keys_with_references() {
let (project, id) = fixture(Some(baseline(vec![])));
let mut draft = Draft::from_project(&project, id).unwrap();
add_header(&mut draft);
let side = format!("{FACETS}/0/Header/Name/oa");
draft
.shape(&format!("{VARIANT}{side}"), json!({"Value":""}))
.unwrap();
let path = format!("{side}/Value");
assert!(free_string(&path));
draft.set_exact(true).unwrap();
let value = "00000000-0000-0000-0000-000000000077\tΩ\r\nname";
draft.input(&path, serde_json::to_string(value).unwrap());
draft
.append(
FACETS,
choice(FacetSelector::Literal(KeyPair { oa: None, sa: None })),
)
.unwrap();
let key = format!("{FACETS}/1/Literal/keys/oa");
draft
.shape(&format!("{VARIANT}{key}"), json!("literal"))
.unwrap();
draft.input(&key, serde_json::to_string("literal\tΩ").unwrap());
draft
.shape(
&format!("{VARIANT}{FACETS}/1/Literal/review/oa"),
json!({"Value":true}),
)
.unwrap();
assert!(free_string(&key));
let mut expected = project.clone();
expected
.traces
.get_mut(&id)
.unwrap()
.baseline
.as_mut()
.unwrap()
.semantic = Some(SemanticBaseline {
facets: vec![
FacetReview::Header(HeaderReview::Name(Reviewed {
oa: Observed::Value(value.into()),
sa: Observed::NotApplicable,
})),
FacetReview::Literal {
keys: KeyPair {
oa: Some("literal\tΩ".into()),
sa: None,
},
review: Reviewed {
oa: Observed::Value(true),
sa: Observed::NotApplicable,
},
},
],
});
assert_history_transaction(&mut document(&project), &draft, &expected);
}
#[test]
fn history_primitive_type_choices_remain_visible_inside_the_tagged_union() {
let schema = resolve(named("TypeRef").unwrap()).unwrap();
let branches = alternatives(schema).unwrap();
let primitives = branches
.iter()
.find(|branch| {
resolve(branch)
.unwrap()
.get("enum")
.and_then(Value::as_array)
.is_some_and(|values| values.len() > 1)
})
.unwrap();
assert!(
branch_has_fields(primitives),
"the primitive branch needs an inner picker, not only its outer union label"
);
let (project, id) = fixture(Some(baseline(vec![])));
let mut draft = Draft::from_project(&project, id).unwrap();
draft
.append(
FACETS,
json!(FacetReview::Field(FieldReview::Type(Reviewed {
oa: Observed::Value(TypeRef::Named(Id::from_u128(888))),
sa: Observed::NotApplicable,
}))),
)
.unwrap();
let path = format!("{FACETS}/0/Field/Type/oa/Value");
draft
.shape(
&format!("{VARIANT}{path}"),
branch_seed(primitives).unwrap(),
)
.unwrap();
for value in [
TypeRef::Boolean,
TypeRef::Integer,
TypeRef::Real,
TypeRef::String,
] {
assert!(matches_value(primitives, &json!(value), 0));
draft.shape(&path, json!(value)).unwrap();
let mut expected = project.clone();
expected
.traces
.get_mut(&id)
.unwrap()
.baseline
.as_mut()
.unwrap()
.semantic = Some(SemanticBaseline {
facets: vec![FacetReview::Field(FieldReview::Type(Reviewed {
oa: Observed::Value(value),
sa: Observed::NotApplicable,
}))],
});
assert_history_transaction(&mut document(&project), &draft, &expected);
}
}
#[test]
fn history_retained_text_constructors_follow_exact_mode_without_changing_the_observation() {
let (project, id) = fixture(Some(baseline(vec![])));
let mut draft = Draft::from_project(&project, id).unwrap();
add_header(&mut draft);
let side = format!("{FACETS}/0/Header/Name/oa");
let path = format!("{side}/Value");
let stored = "Earlier \"quoted\" name\tΩ\r\nsecond line";
draft
.shape(&format!("{VARIANT}{side}"), json!({"Value":""}))
.unwrap();
draft.input(&path, stored.into());
draft
.shape(&format!("{VARIANT}{side}"), json!("NotApplicable"))
.unwrap();
draft.set_exact(true).unwrap();
draft
.shape(&format!("{VARIANT}{side}"), json!({"Value":""}))
.unwrap();
assert_eq!(
draft.inputs[&path].raw,
serde_json::to_string(stored).unwrap()
);
draft
.shape(&format!("{VARIANT}{side}"), json!("NotApplicable"))
.unwrap();
draft.set_exact(false).unwrap();
draft
.shape(&format!("{VARIANT}{side}"), json!({"Value":""}))
.unwrap();
assert_eq!(draft.inputs[&path].raw, stored);
let mut expected = project.clone();
expected
.traces
.get_mut(&id)
.unwrap()
.baseline
.as_mut()
.unwrap()
.semantic = Some(SemanticBaseline {
facets: vec![FacetReview::Header(HeaderReview::Name(Reviewed {
oa: Observed::Value(stored.into()),
sa: Observed::NotApplicable,
}))],
});
assert_history_transaction(&mut document(&project), &draft, &expected);
}
#[test]
fn history_clear_discards_inactive_invalid_constructors_before_reusing_the_same_row() {
let (project, id) = fixture(Some(baseline(vec![])));
let mut draft = Draft::from_project(&project, id).unwrap();
add_header(&mut draft);
let side = format!("{FACETS}/0/Header/Name/oa");
let path = format!("{side}/Value");
draft
.shape(&format!("{VARIANT}{side}"), json!({"Value":""}))
.unwrap();
draft.set_exact(true).unwrap();
draft.input(&path, "\"unfinished".into());
draft
.shape(&format!("{VARIANT}{side}"), json!("NotApplicable"))
.unwrap();
assert!(
draft.set_exact(false).is_err(),
"invalid retained quoted text cannot silently change modes"
);
assert!(draft.exact_strings);
draft.shape(ROOT, Value::Null).unwrap();
assert!(draft.constructor_drafts.is_empty());
draft.set_exact(false).unwrap();
draft.shape(ROOT, json!(baseline(vec![]))).unwrap();
add_header(&mut draft);
draft
.shape(
&format!("{VARIANT}{side}"),
json!({"Value":"Fresh observation"}),
)
.unwrap();
assert_eq!(draft.inputs[&path].raw, "Fresh observation");
let mut expected = project.clone();
expected
.traces
.get_mut(&id)
.unwrap()
.baseline
.as_mut()
.unwrap()
.semantic = Some(SemanticBaseline {
facets: vec![FacetReview::Header(HeaderReview::Name(Reviewed {
oa: Observed::Value("Fresh observation".into()),
sa: Observed::NotApplicable,
}))],
});
assert_history_transaction(&mut document(&project), &draft, &expected);
}