Files
ARCADIA-Studio/harness/probes/chain_context_resolution.rs
T

438 lines
13 KiB
Rust

//! Design probe only: no production schema changes, no execution semantics.
//! Compile directly against an existing oa_core rlib; see the adjacent design.
use oa_core::*;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Endpoint {
references: Vec<Id>,
occurrence: Id,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Address {
owner: Id,
endpoint: Endpoint,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Resolved {
address: Address,
work: Option<Id>,
}
#[derive(Clone, Debug)]
enum Purpose {
Precedence { guard: String },
Exchange { exchange: Id },
CommonWork { work: Id },
}
struct Record {
parent: Option<Id>,
layer: Layer,
kind: Kind,
reference: Option<Id>,
work: Option<Id>,
contribution: bool,
}
struct Index<'a> {
project: &'a Project,
records: BTreeMap<Id, Record>,
cache: BTreeMap<Address, Resolved>,
max_depth: usize,
cache_capacity: usize,
}
impl<'a> Index<'a> {
fn new(project: &'a Project, max_depth: usize, cache_capacity: usize) -> Self {
let records = project
.elements
.values()
.map(|e| {
let (reference, work, contribution) = match e.shared() {
Some(SharedBody::Occurrence { body, .. }) => match body {
OccurrenceBody::SequenceReference { sequence } => (*sequence, None, true),
OccurrenceBody::Action { action, .. } => (None, *action, true),
OccurrenceBody::Exchange { .. } | OccurrenceBody::Control { .. } => {
(None, None, true)
}
_ => (None, None, false),
},
_ => (None, None, false),
};
(
e.id,
Record {
parent: e.parent,
layer: e.layer(),
kind: e.kind(),
reference,
work,
contribution,
},
)
})
.collect();
Self {
project,
records,
cache: BTreeMap::new(),
max_depth,
cache_capacity,
}
}
fn resolve(&mut self, owner: Id, endpoint: &Endpoint) -> Result<Resolved, &'static str> {
if endpoint.references.len() > self.max_depth {
return Err("reference depth limit");
}
let address = Address {
owner,
endpoint: endpoint.clone(),
};
if let Some(resolved) = self.cache.get(&address) {
return Ok(resolved.clone());
}
let root = self.records.get(&owner).ok_or("missing owner")?;
if !matches!(root.kind, Kind::Process | Kind::FunctionalChain) {
return Err("wrong owner kind");
}
let mut current = owner;
let mut visited = BTreeSet::from([owner]);
for step in &endpoint.references {
let reference = self.records.get(step).ok_or("missing path component")?;
if reference.parent != Some(current) || reference.layer != root.layer {
return Err("path is not direct in this context");
}
let next = reference
.reference
.ok_or("path component is not a resolved sequence reference")?;
let target = self
.records
.get(&next)
.ok_or("missing referenced sequence")?;
if target.layer != root.layer || target.kind != root.kind {
return Err("incompatible referenced sequence");
}
if !visited.insert(next) {
return Err("recursive sequence reuse");
}
current = next;
}
let leaf = self
.records
.get(&endpoint.occurrence)
.ok_or("missing leaf")?;
if leaf.parent != Some(current) || leaf.layer != root.layer || !leaf.contribution {
return Err("leaf is not a direct contribution of the resolved owner");
}
let resolved = Resolved {
address: address.clone(),
work: leaf.work,
};
// Cache residency cannot change the validity of an authored address.
// The production implementation can use LRU; deterministic eviction is
// enough to falsify the earlier history-dependent admission mistake.
if self.cache_capacity > 0 {
if self.cache.len() >= self.cache_capacity {
self.cache.pop_first();
}
self.cache.insert(address, resolved.clone());
}
Ok(resolved)
}
fn resolve_many(
&mut self,
owner: Id,
endpoints: &[Endpoint],
max_unique: usize,
) -> Result<Vec<Resolved>, &'static str> {
let unique: BTreeSet<_> = endpoints.iter().collect();
if unique.len() > max_unique {
return Err("analysis address limit");
}
endpoints
.iter()
.map(|endpoint| self.resolve(owner, endpoint))
.collect()
}
fn connection(
&mut self,
owner: Id,
source: &Endpoint,
target: &Endpoint,
purpose: &Purpose,
) -> Result<(), &'static str> {
let a = self.resolve(owner, source)?;
let b = self.resolve(owner, target)?;
if a.address == b.address {
return Err("same contextual occurrence");
}
match purpose {
Purpose::Precedence { guard } => {
let _opaque_guard = guard;
Ok(())
}
Purpose::CommonWork { work } if a.work == Some(*work) && b.work == Some(*work) => {
Ok(())
}
Purpose::CommonWork { .. } => Err("common work does not match both contributions"),
Purpose::Exchange { exchange } => {
let relation = self
.project
.relations
.get(exchange)
.ok_or("missing exchange")?;
if !matches!(
relation.kind(),
RelationKind::Interaction | RelationKind::FunctionalExchange
) || a.work != Some(relation.source())
|| b.work != Some(relation.target())
{
return Err("exchange endpoints do not match contextual work");
}
Ok(())
}
}
}
}
fn id(value: u128) -> Id {
Id::from_u128(value)
}
fn element(p: &mut Project, n: u128, kind: Kind, layer: Layer, parent: Option<Id>) -> Id {
let mut e = p
.make_element(kind, layer, &format!("item_{n}"), parent)
.unwrap();
e.id = id(n);
p.elements.insert(e.id, e);
id(n)
}
fn occurrence(p: &mut Project, n: u128, layer: Layer, owner: Id, body: OccurrenceBody) -> Id {
let key = element(p, n, Kind::Occurrence, layer, Some(owner));
*p.elements.get_mut(&key).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
body,
precondition: String::new(),
postcondition: String::new(),
};
p.elements
.get_mut(&owner)
.unwrap()
.steps_mut()
.unwrap()
.push(key);
key
}
fn endpoint(path: &[u128], leaf: u128) -> Endpoint {
Endpoint {
references: path.iter().copied().map(id).collect(),
occurrence: id(leaf),
}
}
fn run(layer: Layer) {
let mut p = Project::empty();
p.id = id(1000);
let chain = if layer == Layer::Oa {
Kind::Process
} else {
Kind::FunctionalChain
};
let work_kind = if layer == Layer::Oa {
Kind::Activity
} else {
Kind::Function
};
for n in 1..=4 {
element(&mut p, n, chain, layer, None);
}
for n in 10..=13 {
element(&mut p, n, work_kind, layer, None);
}
for (n, owner, work) in [
(20, 2, 10),
(21, 2, 11),
(22, 2, 12),
(23, 3, 11),
(24, 1, 13),
] {
occurrence(
&mut p,
n,
layer,
id(owner),
OccurrenceBody::Action {
action: Some(id(work)),
lifeline: None,
},
);
}
for (n, owner, target) in [(30, 1, 2), (31, 1, 2), (32, 1, 4), (33, 4, 2), (34, 1, 3)] {
occurrence(
&mut p,
n,
layer,
id(owner),
OccurrenceBody::SequenceReference {
sequence: Some(id(target)),
},
);
}
for (n, source, target) in [(40, 20, 21), (41, 21, 22)] {
p.relations.insert(
id(n),
Relation {
id: id(n),
alias: format!("link_{n}"),
name: format!("link_{n}"),
description: String::new(),
body: RelationBody::SequenceLink {
source: id(source),
target: id(target),
guard: String::new(),
},
},
);
}
let exchange = p.relate(
if layer == Layer::Oa {
RelationKind::Interaction
} else {
RelationKind::FunctionalExchange
},
"X supplies B",
id(13),
id(11),
);
assert!(
execute_batch(&p, &[]).is_ok(),
"Probe fixture must be admitted by the existing core"
);
let before = p.clone();
assert!(!validation::can_connect(
&p,
RelationKind::SequenceLink,
id(24),
id(21)
));
assert!(
execute_batch(
&p,
&[Command::Connect(
RelationKind::SequenceLink,
"unqualified".into(),
id(24),
id(21)
)]
)
.is_err()
);
let (x, r1b, r2b, nested_b, qb) = (
endpoint(&[], 24),
endpoint(&[30], 21),
endpoint(&[31], 21),
endpoint(&[32, 33], 21),
endpoint(&[34], 23),
);
let mut index = Index::new(&p, 32, 128);
let first = index.resolve(id(1), &r1b).unwrap();
let second = index.resolve(id(1), &r2b).unwrap();
let nested = index.resolve(id(1), &nested_b).unwrap();
assert_ne!(first.address, second.address);
assert_ne!(first.address, nested.address);
assert_eq!(first.work, second.work);
assert_eq!(first.work, nested.work);
for purpose in [
Purpose::Precedence {
guard: "pressure > 400".into(),
},
Purpose::Exchange { exchange },
] {
index.connection(id(1), &x, &r1b, &purpose).unwrap();
}
index
.connection(id(1), &r1b, &qb, &Purpose::CommonWork { work: id(11) })
.unwrap();
assert!(
index
.connection(id(1), &x, &qb, &Purpose::CommonWork { work: id(11) })
.is_err()
);
assert!(
index
.connection(id(1), &r1b, &x, &Purpose::Exchange { exchange })
.is_err()
);
assert!(
index
.connection(
id(1),
&r1b,
&r1b,
&Purpose::Precedence {
guard: String::new()
}
)
.is_err()
);
for bad in [
endpoint(&[], 21),
endpoint(&[30], 23),
endpoint(&[33], 21),
endpoint(&[30, 33], 21),
endpoint(&[24], 21),
endpoint(&[999], 21),
] {
assert!(
index.resolve(id(1), &bad).is_err(),
"accepted invalid path {bad:?}"
);
}
let mut depth_limited = Index::new(&p, 1, 128);
assert_eq!(
depth_limited.resolve(id(1), &nested_b),
Err("reference depth limit")
);
let mut count_limited = Index::new(&p, 32, 1);
count_limited.resolve(id(1), &r1b).unwrap();
count_limited.resolve(id(1), &r1b).unwrap();
count_limited.resolve(id(1), &r2b).unwrap();
count_limited.resolve(id(1), &r1b).unwrap();
assert_eq!(count_limited.cache.len(), 1);
assert_eq!(
count_limited.resolve_many(id(1), &[r1b.clone(), r2b.clone()], 1),
Err("analysis address limit")
);
assert_eq!(
count_limited.resolve_many(id(1), &[r2b.clone(), r1b.clone()], 1),
Err("analysis address limit")
);
assert_eq!(
count_limited
.resolve_many(id(1), &[r1b.clone(), r2b.clone()], 2)
.unwrap()
.len(),
2
);
let mut changed = p.clone();
if let Some(SharedBody::Occurrence {
body: OccurrenceBody::SequenceReference { sequence },
..
}) = changed.elements.get_mut(&id(30)).unwrap().shared_mut()
{
*sequence = Some(id(3));
}
assert!(Index::new(&changed, 32, 128).resolve(id(1), &r1b).is_err());
let mut deleted = p.clone();
deleted.elements.remove(&id(30));
assert!(Index::new(&deleted, 32, 128).resolve(id(1), &r1b).is_err());
assert_eq!(
p, before,
"Address resolution may not mutate shared procedures"
);
println!(
"{layer:?}: existing foreign link rejected; R1/R2/nested distinct; 3 purposes checked; invalid paths/retarget/delete/limits refused; shared model unchanged"
);
}
fn main() {
run(Layer::Oa);
run(Layer::Sa);
println!(
"PASS: bounded contextual resolver design probe (not production schema/API acceptance)"
);
}