Integrate release recovery, semantic review, coverage and Windows packaging

This commit is contained in:
Codex
2026-09-06 16:31:55 -07:00
parent c091f80e6e
commit 2b2d2bc18b
174 changed files with 27635 additions and 127 deletions
+33 -18
View File
@@ -402,6 +402,37 @@ fn checked_in_source_project() -> SourceBundle {
}
}
fn assert_architecture_example_warnings(compiled: &crate::source_project::CompiledSourceProject) {
// This architecture-only example intentionally has no process/scenario
// context. Preserve its explicit incompleteness instead of inventing work.
let expected: std::collections::BTreeSet<_> = [
("unspecified_exchange_mechanism", "oa_report_data"),
("unspecified_exchange_mechanism", "sa_report_data"),
("work_without_behavior_context", "report_distress"),
("work_without_behavior_context", "assess_incident"),
("work_without_behavior_context", "transmit_report"),
("work_without_behavior_context", "assess_report"),
("work_without_behavior_context", "review_response"),
("exchange_without_behavior_context", "distress_signal"),
("exchange_without_behavior_context", "validated_report"),
("capability_without_behavior_context", "protect_life"),
("capability_without_behavior_context", "coordinate_response"),
]
.into_iter()
.map(|(code, alias)| (code, Some(identity(&compiled.decoded.project, alias))))
.collect();
let actual: std::collections::BTreeSet<_> = compiled
.diagnostics
.iter()
.map(|issue| {
assert_eq!(issue.diagnostic.severity, validation::Severity::Warning);
(issue.diagnostic.code.as_str(), issue.diagnostic.object_id)
})
.collect();
assert_eq!(actual, expected);
assert_eq!(compiled.diagnostics.len(), expected.len());
}
#[test]
fn checked_in_nested_example_has_stable_ids_typed_exchanges_and_three_clear_views() {
let input = checked_in_source_project();
@@ -428,15 +459,7 @@ fn checked_in_nested_example_has_stable_ids_typed_exchanges_and_three_clear_view
let reopened = compile_bundle(&compiled.bundle, ProjectLimits::default()).unwrap();
assert_eq!(reopened.bundle, compiled.bundle);
assert!(reopened.decoded.migration.is_none());
assert_eq!(compiled.diagnostics.len(), 2);
assert!(
compiled
.diagnostics
.iter()
.all(|issue| issue.diagnostic.code == "unspecified_exchange_mechanism"),
"{:?}",
compiled.diagnostics
);
assert_architecture_example_warnings(&compiled);
let project = &compiled.decoded.project;
assert_eq!(project.diagrams.len(), 3);
let assess = identity(project, "assess_report");
@@ -495,15 +518,7 @@ fn checked_in_example_supports_cross_file_reparent_without_changing_allocations(
ProjectLimits::default(),
)
.unwrap();
assert_eq!(result.diagnostics.len(), 2);
assert!(
result
.diagnostics
.iter()
.all(|issue| issue.diagnostic.code == "unspecified_exchange_mechanism"),
"{:?}",
result.diagnostics
);
assert_architecture_example_warnings(&result);
assert_eq!(
result.index.declarations[&assess].path,
"sa/assurance.arcadia"
+6 -1
View File
@@ -53,7 +53,12 @@ pub fn state_header_height(p: &Project, element: Id) -> f32 {
if *source == element)
})
.count();
64. + if actions { 28. } else { 0. } + internal as f32 * 56.
let typed_actions = p
.relations
.values()
.filter(|r| r.kind() == RelationKind::StateAction && r.source() == element)
.count();
64. + if actions { 28. } else { 0. } + typed_actions as f32 * 28. + internal as f32 * 56.
}
pub(crate) fn node_size(p: &Project, e: &Element, size: Size, collapsed: bool) -> Size {
+23 -8
View File
@@ -350,10 +350,7 @@ pub(crate) fn write_fact(
else {
return Err("Target has no message annotations".into());
};
values.retain(|v| AnnotationKey::from(v) != *key);
if let Some(row) = row {
values.push(row);
}
replace_keyed_row(values, row, |v| AnnotationKey::from(v) == *key);
Ok(())
}
FacetReview::Binding { keys, review } => {
@@ -374,10 +371,7 @@ pub(crate) fn write_fact(
else {
return Err("Target has no scenario bindings".into());
};
bindings.retain(|v| v.referenced != key);
if let Some(row) = row {
bindings.push(row);
}
replace_keyed_row(bindings, row, |v| v.referenced == key);
Ok(())
}
FacetReview::Child { keys, review } => {
@@ -467,6 +461,27 @@ pub(crate) fn write_fact(
}
}
// Reviewing an unordered row does not authorize changing its authored position.
// Preserve surviving entries in place; only genuinely new rows append.
fn replace_keyed_row<T: PartialEq>(
rows: &mut Vec<T>,
row: Option<T>,
matches: impl FnMut(&T) -> bool,
) {
match (rows.iter().position(matches), row) {
(Some(index), Some(row)) => {
if rows[index] != row {
rows[index] = row;
}
}
(Some(index), None) => {
rows.remove(index);
}
(None, Some(row)) => rows.push(row),
(None, None) => {}
}
}
pub(crate) fn constructor_contains(group: &FacetSelector, facet: &FacetSelector) -> bool {
use FacetSelector as F;
match group {
+9 -7
View File
@@ -2,6 +2,8 @@ use crate::*;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
mod coverage;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Severity {
Error,
@@ -179,6 +181,7 @@ fn check_ids(out: &mut Vec<Issue>, owner: Id, field: &str, ids: &[Id]) {
pub fn validate(p: &Project) -> Vec<Issue> {
let mut out = Vec::new();
let coverage = coverage::Index::new(p);
if p.schema_version != SCHEMA_VERSION {
error(
&mut out,
@@ -346,19 +349,15 @@ pub fn validate(p: &Project) -> Vec<Issue> {
);
}
if matches!(e.kind(), Kind::Capability | Kind::SystemCapability)
&& !p.relations.values().any(|r| {
matches!(
r.kind(),
RelationKind::CapabilityInvolvement | RelationKind::ChainInvolvement
) && r.target() == e.id
})
&& !coverage.grouping.contains(&e.id)
&& !coverage.supported_capabilities.contains(&e.id)
{
warning(
&mut out,
e.id,
"unsupported_capability",
"involvement",
"Connect the capability to involved participants, work, or a process/chain.",
"Connect the capability to involved participants, work, a process/chain, or an explicit scenario context.",
);
}
if let Some(steps) = e.steps() {
@@ -921,6 +920,9 @@ pub fn validate(p: &Project) -> Vec<Issue> {
);
}
}
if !out.iter().any(|issue| issue.severity == Severity::Error) {
coverage.validate(p, &mut out);
}
out
}
fn validate_sequence(p: &Project, e: &Element, steps: &[Id], out: &mut Vec<Issue>) {
+283
View File
@@ -0,0 +1,283 @@
//! Advisory presence of explicit behavior evidence, never inferred execution.
use super::*;
#[derive(Default)]
pub(super) struct Index {
pub grouping: BTreeSet<Id>,
pub supported_capabilities: BTreeSet<Id>,
contextual_capabilities: BTreeSet<Id>,
supported_missions: BTreeSet<Id>,
}
impl Index {
pub fn new(p: &Project) -> Self {
let mut index = Self::default();
for element in p.elements.values() {
if let Some(parent) = element.parent.and_then(|id| p.elements.get(&id))
&& parent.kind() == element.kind()
&& parent.layer() == element.layer()
{
index.grouping.insert(parent.id);
}
if let Some(SharedBody::Scenario {
context: Some(id), ..
}) = element.shared()
&& p.elements.get(id).is_some_and(|context| {
matches!(context.kind(), Kind::Capability | Kind::SystemCapability)
&& context.layer() == element.layer()
})
{
index.contextual_capabilities.insert(*id);
index.supported_capabilities.insert(*id);
}
}
for relation in p.relations.values() {
let (Some(source), Some(target)) = (
p.elements.get(&relation.source()),
p.elements.get(&relation.target()),
) else {
continue;
};
if source.layer() != target.layer()
|| !relation.kind().permits(source.kind(), target.kind())
{
continue;
}
match relation.kind() {
RelationKind::CapabilityInvolvement => {
index.supported_capabilities.insert(target.id);
}
RelationKind::ChainInvolvement => {
index.supported_capabilities.insert(target.id);
index.contextual_capabilities.insert(target.id);
}
RelationKind::MissionCapability => {
index.supported_missions.insert(source.id);
}
_ => {}
}
}
index
}
pub fn validate(&self, p: &Project, out: &mut Vec<Issue>) {
let (work, exchanges) = included_evidence(p);
for element in p.elements.values() {
if element.kind().is_work()
&& !self.grouping.contains(&element.id)
&& !work.contains(&element.id)
{
warning(
out,
element.id,
"work_without_behavior_context",
"",
"Include this leaf activity/function in a process/chain or scenario through a work action or exchange occurrence.",
);
}
if matches!(element.kind(), Kind::OperationalMission | Kind::Mission)
&& !self.supported_missions.contains(&element.id)
{
warning(
out,
element.id,
"mission_without_capability",
"",
"Connect this mission to a capability that delivers it.",
);
}
if matches!(element.kind(), Kind::Capability | Kind::SystemCapability)
&& !self.grouping.contains(&element.id)
&& self.supported_capabilities.contains(&element.id)
&& !self.contextual_capabilities.contains(&element.id)
{
warning(
out,
element.id,
"capability_without_behavior_context",
"",
"Add an explicit behavior context: link a process/chain with Chain involvement, or set a scenario's Context to this capability.",
);
}
}
for relation in p.relations.values().filter(|r| work_exchange(r.kind())) {
let mut non_leaf = false;
for (field, endpoint) in [("source", relation.source()), ("target", relation.target())]
{
if self.grouping.contains(&endpoint) {
non_leaf = true;
warning(
out,
relation.id,
"non_leaf_work_exchange",
field,
"Choose the leaf activity/function responsible for this exchange; keep any typed port owned by that function.",
);
}
}
if !non_leaf && !exchanges.contains(&relation.id) {
warning(
out,
relation.id,
"exchange_without_behavior_context",
"",
"Use this exchange in a process/chain or scenario.",
);
}
}
}
}
fn work_exchange(kind: RelationKind) -> bool {
matches!(
kind,
RelationKind::Interaction | RelationKind::FunctionalExchange
)
}
/// Only ordered, owned occurrences contribute. Each identity is visited once,
/// including reused containers; this remains bounded even on malformed drafts.
fn included_evidence(p: &Project) -> (BTreeSet<Id>, BTreeSet<Id>) {
let mut pending: Vec<_> = p
.elements
.values()
.filter(|e| {
matches!(
e.kind(),
Kind::Process | Kind::FunctionalChain | Kind::Scenario
)
})
.map(|e| e.id)
.collect();
let mut seen = BTreeSet::new();
let mut work = BTreeSet::new();
let mut exchanges = BTreeSet::new();
while let Some(id) = pending.pop() {
if !seen.insert(id) {
continue;
}
let Some(element) = p.elements.get(&id) else {
continue;
};
if let Some(steps) = element.steps() {
pending.extend(
steps
.iter()
.filter(|step| {
p.elements.get(step).is_some_and(|child| {
child.kind() == Kind::Occurrence
&& child.parent == Some(id)
&& child.layer() == element.layer()
})
})
.copied(),
);
}
let Some(SharedBody::Occurrence { body, .. }) = element.shared() else {
continue;
};
match body {
OccurrenceBody::Action {
action: Some(action),
..
} => {
if p.elements
.get(action)
.is_some_and(|e| e.kind().is_work() && e.layer() == element.layer())
{
work.insert(*action);
}
}
OccurrenceBody::Exchange {
exchange: Some(exchange),
..
} => {
if let Some(relation) = p.relations.get(exchange)
&& work_exchange(relation.kind())
&& [relation.source(), relation.target()].iter().all(|id| {
p.elements
.get(id)
.is_some_and(|e| e.kind().is_work() && e.layer() == element.layer())
})
{
exchanges.insert(*exchange);
work.extend([relation.source(), relation.target()]);
}
}
OccurrenceBody::Fragment { branches, .. } => {
pending.extend(
branches
.iter()
.filter(|branch| {
p.elements.get(branch).is_some_and(|child| {
child.parent == Some(id)
&& child.layer() == element.layer()
&& matches!(
child.shared(),
Some(SharedBody::Occurrence {
body: OccurrenceBody::Branch { .. },
..
})
)
})
})
.copied(),
);
}
OccurrenceBody::SequenceReference {
sequence: Some(sequence),
} => {
if let Some(owner) = element.parent.and_then(|id| p.elements.get(&id))
&& matches!(owner.kind(), Kind::Process | Kind::FunctionalChain)
&& p.elements.get(sequence).is_some_and(|target| {
target.kind() == owner.kind() && target.layer() == element.layer()
})
{
pending.push(*sequence);
}
}
OccurrenceBody::ScenarioUse {
scenario: Some(scenario),
..
} if p.elements.get(scenario).is_some_and(|target| {
target.kind() == Kind::Scenario && target.layer() == element.layer()
}) =>
{
pending.push(*scenario);
}
_ => {}
}
}
(work, exchanges)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn malformed_self_reuse_is_bounded_even_without_structural_gate() {
let mut p = Project::empty();
let scenario = p
.add_element(Kind::Scenario, Layer::Oa, "Self reuse", None)
.unwrap();
let occurrence = p
.add_element(Kind::Occurrence, Layer::Oa, "Cycle", Some(scenario))
.unwrap();
if let Some(SharedBody::Occurrence { body, .. }) =
p.elements.get_mut(&occurrence).unwrap().shared_mut()
{
*body = OccurrenceBody::ScenarioUse {
scenario: Some(scenario),
bindings: vec![],
};
}
p.elements
.get_mut(&scenario)
.unwrap()
.steps_mut()
.unwrap()
.push(occurrence);
assert_eq!(included_evidence(&p), (BTreeSet::new(), BTreeSet::new()));
}
}