Compare commits

..
18 changed files with 2387 additions and 25 deletions
+72 -8
View File
@@ -3,7 +3,7 @@ mod raw_tokens;
#[path = "language/semantic_source.rs"]
mod semantic_source;
mod source3_bridge;
// Staged syntax foundation only. Public source8 admission waits for complete
// Staged typed analysis only. Public mixed source8 admission waits for complete
// declaration lowering, source updates and the root-owned resource gate.
#[cfg(test)]
mod analysis_source;
@@ -338,7 +338,7 @@ fn enum_field(key: &str) -> bool {
)
}
/// Parse V5, or explicitly migrate a valid V1/V2/V3/V4 source candidate. Parsing never
/// Parse ordinary V8, or explicitly migrate a supported older source candidate. Parsing never
/// adopts a model and never overwrites an editor draft.
pub fn parse(source: &str) -> Result<Project, Vec<Diagnostic>> {
parse_limited(source, ParseLimits::UNLIMITED)
@@ -1028,6 +1028,18 @@ fn edit_positions_from_tokens_with_text(
count: usize,
value_ends: Option<&BTreeMap<usize, usize>>,
text: impl FnMut(usize, Span) -> Result<(), Box<Diagnostic>>,
) -> Result<Vec<(Id, EditDeclaration)>, Box<Diagnostic>> {
edit_positions_from_tokens_with_producers(source, unit, token, count, value_ends, None, text)
}
fn edit_positions_from_tokens_with_producers(
source: &str,
unit: &SourceUnit,
token: impl Fn(usize) -> Lexeme,
count: usize,
value_ends: Option<&BTreeMap<usize, usize>>,
mut quota: Option<usize>,
text: impl FnMut(usize, Span) -> Result<(), Box<Diagnostic>>,
) -> Result<Vec<(Id, EditDeclaration)>, Box<Diagnostic>> {
let mut ends: Vec<_> = (0..count).map(|at| token(at).span.end).collect();
let mut stack = Vec::new();
@@ -1044,10 +1056,27 @@ fn edit_positions_from_tokens_with_text(
ends: Vec<usize>,
value_ends: Option<&'a BTreeMap<usize, usize>>,
text: H,
source: &'a str,
quota: &'a mut Option<usize>,
}
impl<F: Fn(usize) -> Lexeme, H: FnMut(usize, Span) -> Result<(), Box<Diagnostic>>>
Positions<'_, F, H>
{
fn consume(&mut self, slots: usize, offset: usize) -> Result<(), Box<Diagnostic>> {
if let Some(remaining) = self.quota {
*remaining = remaining.checked_sub(slots).ok_or_else(|| {
Box::new(diagnostic(
self.source,
Error {
code: "invalid_source",
offset,
message: "Source8 projection quota underflow.".into(),
},
))
})?;
}
Ok(())
}
fn token(&self, start: usize) -> usize {
let mut low = 0;
let mut high = self.ends.len();
@@ -1068,6 +1097,7 @@ fn edit_positions_from_tokens_with_text(
fields
.iter()
.map(|(key, ast)| {
self.consume(1, ast.offset())?;
let value = self.value(ast)?;
let start = (self.token_at)(self.token(value.span.start) - 1).span.start;
let end = (self.token_at)(self.token(value.span.end)).span.end;
@@ -1083,6 +1113,7 @@ fn edit_positions_from_tokens_with_text(
.collect()
}
fn value(&mut self, ast: &Ast) -> Result<EditValue, Box<Diagnostic>> {
self.consume(1, ast.offset())?;
let index = self.token(ast.offset());
let span = Span {
start: ast.offset(),
@@ -1119,8 +1150,11 @@ fn edit_positions_from_tokens_with_text(
ends,
value_ends,
text,
source,
quota: &mut quota,
};
unit.declarations
let result = unit
.declarations
.iter()
.map(|declaration| {
if declaration.id_insertion.is_some() {
@@ -1133,6 +1167,7 @@ fn edit_positions_from_tokens_with_text(
},
)));
}
positions.consume(7, declaration.span.start)?;
let roles: &[&str] = if matches!(declaration.kind, DeclKind::Node) {
&["kind", "alias", "id_keyword", "id"]
} else {
@@ -1161,7 +1196,18 @@ fn edit_positions_from_tokens_with_text(
},
))
})
.collect()
.collect::<Result<Vec<_>, _>>()?;
if quota.is_some_and(|remaining| remaining != 0) {
return Err(Box::new(diagnostic(
source,
Error {
code: "invalid_source",
offset: 0,
message: "Source8 projection quota leftover.".into(),
},
)));
}
Ok(result)
}
struct Parser<'a> {
source: &'a str,
@@ -1337,6 +1383,10 @@ impl Parser<'_> {
.span
.end;
self.take(";")?;
#[cfg(test)]
if let Some(mixed) = &mut self.mixed {
mixed.projection(1, Span { start, end })?;
}
Ok((key, value, Span { start, end }))
}
@@ -1370,7 +1420,12 @@ impl Parser<'_> {
.span
.end;
if let Some(mixed) = &mut self.mixed {
mixed.value_ends.insert(ast.offset(), end);
let span = Span {
start: ast.offset(),
end,
};
mixed.value_end(span)?;
mixed.projection(1, span)?;
}
}
result
@@ -1493,12 +1548,12 @@ impl Parser<'_> {
if if mixed_mode {
self.text() != "8"
} else {
!matches!(self.text(), "3" | "4" | "5" | "6" | "7")
!matches!(self.text(), "3" | "4" | "5" | "6" | "7" | "8")
} {
return Err(self.error(if mixed_mode {
"Private mixed syntax requires arcadia 8."
} else {
"Unsupported schema version: expected arcadia 3, 4, 5, 6 or 7."
"Unsupported schema version: expected arcadia 3, 4, 5, 6, 7 or 8."
}));
}
let schema_version = self.text().parse().expect("checked source version");
@@ -1697,7 +1752,12 @@ impl Parser<'_> {
let lexical_owner = open.last().map(|&owner| declarations[owner].id);
#[cfg(test)]
if let Some(mixed) = &mut self.mixed {
mixed.lexical_owners.push(open.last().copied());
let span = Span {
start: offset,
end: body_start,
};
mixed.lexical_owner(open.last().copied(), span)?;
mixed.projection(7, span)?;
}
declarations.push(Declaration {
kind,
@@ -2223,6 +2283,10 @@ pub fn analyze(source: &str) -> Analysis {
#[path = "language_nesting_tests.rs"]
mod nesting_tests;
#[cfg(test)]
#[path = "language_ordinary_source8_tests.rs"]
mod ordinary_source8;
pub(crate) mod legacy {
//! Arcadia text: a readable, lossless representation of a valid OA project.
//!
@@ -39,6 +39,11 @@ pub struct Budget {
numeric: WorkMeter,
failed: Option<Error>,
text: Option<ProducerText>,
entries: Option<ProducerEntries>,
}
struct ProducerEntries {
carried: usize,
metadata: usize,
}
struct ProducerText {
carried: usize,
@@ -69,8 +74,64 @@ impl Budget {
),
failed: None,
text: None,
entries: None,
})
}
pub(super) fn enable_entry_producers(
&mut self,
carried: usize,
files: usize,
) -> Result<(), Error> {
assert!(self.entries.is_none());
self.check_producer_entries(carried, files, 0, Span::default())?;
self.entries = Some(ProducerEntries {
carried,
metadata: files,
});
Ok(())
}
fn check_producer_entries(
&mut self,
carried: usize,
metadata: usize,
delta: usize,
span: Span,
) -> Result<(), Error> {
self.active()?;
let total = carried
.checked_add(self.usage.entries)
.and_then(|n| n.checked_add(metadata))
.and_then(|n| n.checked_add(delta));
let message = match total {
None => Some("Source8 producer entry size overflow."),
Some(n) if n > self.limits.entries => Some("Source8 lowering entry limit exceeded."),
_ => None,
};
if let Some(message) = message {
let failure = error(span, message);
self.failed = Some(failure.clone());
return Err(failure);
}
Ok(())
}
pub(super) fn producer_entries(&self) -> Option<usize> {
self.entries.as_ref().map(|e| e.metadata)
}
pub(super) fn reserve_metadata(&mut self, delta: usize, span: Span) -> Result<(), Error> {
if let Some(e) = &self.entries {
self.check_producer_entries(e.carried, e.metadata, delta, span)?;
self.entries.as_mut().expect("enabled entries").metadata += delta;
}
Ok(())
}
pub(super) fn producer_entry_overflow(&mut self, span: Span) -> Error {
if let Some(failure) = &self.failed {
return failure.clone();
}
let failure = error(span, "Source8 producer entry size overflow.");
self.failed = Some(failure.clone());
failure
}
pub(super) fn enable_text_producers(&mut self, carried: usize) -> Result<(), Error> {
self.index_bytes(carried, Span::default())?;
assert!(self.text.is_none());
@@ -200,6 +261,9 @@ impl Budget {
span,
"syntax entries",
)?;
if let Some(e) = &self.entries {
self.check_producer_entries(e.carried, e.metadata, 1, span)?;
}
self.usage.entries += 1;
Ok(())
}
@@ -380,7 +380,7 @@ fn missing_id_metadata_is_stable_and_parameter_ids_are_owner_scoped() {
}
#[test]
fn inherited_version_root_and_short_word_contracts_do_not_enable_public8() {
fn inherited_version_root_and_short_word_contracts_do_not_enable_public_typed8() {
let source = "oa_check c \"C\" { predicate unbound; }";
for version in [1, 2, 3, 4, 5, 6, 7, 9] {
let mut budget = budget();
@@ -431,8 +431,12 @@ fn inherited_version_root_and_short_word_contracts_do_not_enable_public8() {
let contextual =
declaration("calculation unit \"C\" { input and : Number; result Number; body $and; }");
assert_eq!(contextual.header.alias.name, "unit");
assert!(crate::language::parse("arcadia 8; project p \"P\" {}").is_err());
for version in 3..=7 {
let ordinary = crate::language::parse_with_report("arcadia 8; project p \"P\" {}").unwrap();
assert!(ordinary.migration.is_none());
assert_eq!(ordinary.project.schema_version, 8);
assert_eq!(ordinary.project.alias, "p");
assert_eq!(ordinary.project.name, "P");
for version in 3..=8 {
assert!(
crate::language::parse(&format!(
"arcadia {version}; project p \"P\" {{ {source} }}"
@@ -96,7 +96,16 @@ fn staged_oa_and_sa_lowering_match_complete_independent_17_uuid_oracles() {
assert_eq!(actual, expected);
admit(&actual); // the complete model's one semantic admission
}
assert!(crate::language::parse(&ORDINARY_OA.replacen("arcadia 7", "arcadia 8", 1)).is_err());
for (ordinary_source, typed_source) in [(ORDINARY_OA, OA), (ORDINARY_SA, SA)] {
let current = ordinary_source.replacen("arcadia 7", "arcadia 8", 1);
let decoded = crate::language::parse_with_report(&current).unwrap();
assert!(decoded.migration.is_none());
assert_eq!(decoded.project, ordinary(ordinary_source));
assert!(
crate::language::parse(&format!("arcadia 8; project p \"P\" {{ {typed_source} }}"))
.is_err()
);
}
}
#[test]
@@ -68,8 +68,11 @@ pub(in crate::language) struct State<'a> {
budget: &'a mut Budget,
model: &'a mut ModelCounts,
typed: Vec<declarations::Declaration>,
pub(in crate::language) value_ends: BTreeMap<usize, usize>,
pub(in crate::language) lexical_owners: Vec<Option<usize>>,
value_ends: BTreeMap<usize, usize>,
lexical_owners: Vec<Option<usize>>,
pending_projection: usize,
reserved_values: usize,
reserved_owners: usize,
}
fn ordinary_error(error: super::Error) -> Error {
@@ -93,6 +96,42 @@ fn lexeme(token: lex::Token) -> Lexeme {
}
impl State<'_> {
pub(in crate::language) fn projection(
&mut self,
slots: usize,
span: Span,
) -> Result<(), Error> {
if self.budget.producer_entries().is_some() {
self.pending_projection = self
.pending_projection
.checked_add(slots)
.ok_or_else(|| ordinary_error(self.budget.producer_entry_overflow(span)))?;
}
Ok(())
}
pub(in crate::language) fn value_end(&mut self, span: Span) -> Result<(), Error> {
let new_key = !self.value_ends.contains_key(&span.start);
self.budget
.reserve_metadata(usize::from(new_key), span)
.map_err(ordinary_error)?;
if new_key {
self.reserved_values += 1;
}
self.value_ends.insert(span.start, span.end);
Ok(())
}
pub(in crate::language) fn lexical_owner(
&mut self,
owner: Option<usize>,
span: Span,
) -> Result<(), Error> {
self.budget
.reserve_metadata(1, span)
.map_err(ordinary_error)?;
self.lexical_owners.push(owner);
self.reserved_owners += 1;
Ok(())
}
pub(in crate::language) fn own_text(&mut self, bytes: usize, span: Span) -> Result<(), Error> {
self.budget.own_text(bytes, span).map_err(ordinary_error)
}
@@ -263,7 +302,7 @@ fn parse_with_profile(
limits: ProjectLimits,
profile: Limits,
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
parse_impl(bundle, limits, profile, None, false)
parse_impl(bundle, limits, profile, None, false, false)
}
fn parse_impl<'s>(
@@ -272,6 +311,7 @@ fn parse_impl<'s>(
profile: Limits,
carried: Option<carried::CarriedGuard<'s>>,
text_producers: bool,
entry_producers: bool,
) -> Result<ParsedMixedSyntax<'s>, Vec<SourceDiagnostic>> {
let entry_source = bundle.files.get(&bundle.entry).map_or("", String::as_str);
let at_entry = |error| located(&bundle.entry, entry_source, ordinary_error(error));
@@ -313,7 +353,14 @@ fn parse_impl<'s>(
.index_bytes(bytes, Span::default())
.map_err(at_entry)?;
let mut metadata = bundle.files.len();
if let Some(input) = &carried {
if entry_producers {
let input = carried
.as_ref()
.expect("entry mode requires carried authority");
budget
.enable_entry_producers(input.slots().entries, metadata)
.map_err(at_entry)?;
} else if let Some(input) = &carried {
input.check(&budget, 0, metadata).map_err(at_entry)?;
} else {
budget
@@ -337,6 +384,7 @@ fn parse_impl<'s>(
.expect("traversal checked source");
let failure = |error| located(path, source, ordinary_error(error));
let tokens = lex::tokens(source, &mut budget).map_err(failure)?;
let metadata_before = budget.producer_entries();
let mut parser = Parser {
source,
tokens: Vec::new(),
@@ -351,6 +399,9 @@ fn parse_impl<'s>(
typed: Vec::new(),
value_ends: BTreeMap::new(),
lexical_owners: Vec::new(),
pending_projection: 0,
reserved_values: 0,
reserved_owners: 0,
}),
};
let ordinary = parser
@@ -360,7 +411,19 @@ fn parse_impl<'s>(
drop(parser);
// Projected ordinary AST nodes/fields are bounded before constructing a
// second position representation. Count references as slots, not syntax.
let projection_slots = ordinary_position_slots(&ordinary);
let projection_slots = if entry_producers {
let slots = tokens
.len()
.checked_add(state.pending_projection)
.ok_or_else(|| failure(state.budget.producer_entry_overflow(Span::default())))?;
state
.budget
.reserve_metadata(slots, Span::default())
.map_err(failure)?;
state.pending_projection
} else {
ordinary_position_slots(&ordinary)
};
metadata = metadata
.checked_add(tokens.len())
.and_then(|n| n.checked_add(state.value_ends.len()))
@@ -377,12 +440,13 @@ fn parse_impl<'s>(
.lowering_entries(metadata, Span::default())
.map_err(failure)?;
}
let positions = language::edit_positions_from_tokens_with_text(
let positions = language::edit_positions_from_tokens_with_producers(
source,
&ordinary,
|at| lexeme(tokens[at]),
tokens.len(),
Some(&state.value_ends),
entry_producers.then_some(projection_slots),
|bytes, span| {
state
.budget
@@ -397,6 +461,23 @@ fn parse_impl<'s>(
related: Vec::new(),
}]
})?;
if let Some(before) = metadata_before {
let expected = tokens
.len()
.checked_add(state.value_ends.len())
.and_then(|n| n.checked_add(state.lexical_owners.len()))
.and_then(|n| n.checked_add(projection_slots))
.and_then(|n| n.checked_add(before));
if state.reserved_values != state.value_ends.len()
|| state.reserved_owners != state.lexical_owners.len()
|| expected != state.budget.producer_entries()
{
return Err(failure(super::error(
Span::default(),
"Source8 producer entry inventory mismatch.",
)));
}
}
for (raw, span) in &ordinary.includes {
traversal.include(path, raw, *span)?;
}
@@ -421,6 +502,12 @@ fn parse_impl<'s>(
}
traversal.finish()?;
budget.active().map_err(at_entry)?;
if budget.producer_entries().is_some_and(|m| m != metadata) {
return Err(at_entry(super::error(
Span::default(),
"Source8 producer entry inventory mismatch.",
)));
}
if let Some((_, temporary)) = budget.producer_text() {
assert_eq!(temporary, 0, "completed syntax has no temporary text");
}
@@ -1,7 +1,8 @@
//! Fixture-constructible carried phase authority, not an emitted-output result.
//! Complete pending-backed authority and fixture-only carried snapshots.
//! Checks existing logical phase totals; does not meter every parser allocation.
use super::*;
use project::accounting::{Ledger, Slots};
use project::ordinary_index::rename::emission::PendingRename;
struct SnapshotPolicy {
limits: Limits,
@@ -15,6 +16,7 @@ struct RetainedSnapshot {
retained: Slots,
}
enum SnapshotBacking<'s> {
Pending(&'s PendingRename<'s>),
Borrowed {
bundle: &'s SourceBundle,
policy: SnapshotPolicy,
@@ -27,6 +29,7 @@ pub(super) struct CarriedInput<'s> {
backing: SnapshotBacking<'s>,
}
enum GuardBacking<'s> {
Pending(&'s PendingRename<'s>),
Borrowed,
Retained(&'s RetainedSnapshot),
}
@@ -37,8 +40,26 @@ pub(super) struct CarriedGuard<'s> {
backing: GuardBacking<'s>,
}
impl<'s> CarriedInput<'s> {
pub(super) fn from_pending<'t: 's>(pending: &'s PendingRename<'t>) -> Self {
Self {
backing: SnapshotBacking::Pending(pending),
}
}
fn into_parts(self) -> (&'s SourceBundle, SnapshotPolicy, CarriedGuard<'s>) {
match self.backing {
SnapshotBacking::Pending(pending) => {
let (limits, project_limits) = pending.carried_policy();
(
pending.carried_bundle(),
SnapshotPolicy {
limits,
project_limits,
},
CarriedGuard {
backing: GuardBacking::Pending(pending),
},
)
}
SnapshotBacking::Borrowed { bundle, policy } => (
bundle,
policy,
@@ -62,6 +83,7 @@ impl<'s> CarriedInput<'s> {
impl CarriedGuard<'_> {
pub(super) fn slots(&self) -> Slots {
match &self.backing {
GuardBacking::Pending(pending) => pending.carried_slots(),
GuardBacking::Borrowed => Slots::default(),
GuardBacking::Retained(owner) => owner.retained,
}
@@ -91,6 +113,7 @@ fn parse_carried(input: CarriedInput<'_>) -> Result<ParsedMixedSyntax<'_>, Vec<S
policy.limits,
Some(guard),
false,
false,
)
}
@@ -106,6 +129,22 @@ fn parse_text_producers(
policy.limits,
Some(guard),
true,
false,
)
}
/// Combined producer route, selected only by sealed pending or fixture authority.
pub(super) fn parse_entry_producers(
input: CarriedInput<'_>,
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
let (bundle, policy, guard) = input.into_parts();
parse_impl(
bundle,
policy.project_limits,
policy.limits,
Some(guard),
true,
true,
)
}
@@ -163,3 +202,6 @@ mod tests;
#[cfg(test)]
mod text_producer;
#[cfg(test)]
mod entry_producer;
@@ -0,0 +1,445 @@
//! Literal entry oracles, independently authored before the producer implementation.
use super::*;
const LIMIT: &str = "Source8 lowering entry limit exceeded.";
fn doc(body: &str) -> String {
format!(
"arcadia 8;\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {{\n{body}\n}}\n"
)
}
fn bundle(body: &str) -> SourceBundle {
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([("main.arcadia".into(), doc(body))]),
}
}
fn chain() -> SourceBundle {
let mut b = bundle("include \"part.arcadia\";");
b.files.insert(
"part.arcadia".into(),
"include \"last.arcadia\";\r\n".into(),
);
b.files.insert("last.arcadia".into(), "//é\r\n".into());
b
}
fn held(bundle: SourceBundle, entries: usize) -> RetainedSnapshot {
RetainedSnapshot::fixture(
bundle,
SnapshotPolicy {
limits: Limits {
entries,
bytes: 4096,
..Limits::source8(ParseLimits::UNLIMITED)
},
project_limits: ProjectLimits::default(),
},
)
.unwrap()
}
fn failed(result: Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>>) -> SourceDiagnostic {
let errors = match result {
Ok(_) => panic!("unexpected syntax success"),
Err(e) => e,
};
assert_eq!(errors.len(), 1);
errors.into_iter().next().unwrap()
}
fn diagnostic(d: &SourceDiagnostic, path: &str, start: usize, end: usize) {
assert_eq!(d.path, path);
assert_eq!(d.diagnostic.code, "invalid_source");
assert_eq!(d.diagnostic.message, LIMIT);
assert_eq!(d.diagnostic.span, Span { start, end });
assert!(d.related.is_empty());
}
#[test]
fn real_owner_syntax_and_resolver_boundaries_are_separate() {
for (b, c, u, m, syntax_cap, resolve_cap, refused_path) in [
(bundle(""), 1, 1, 11, 13, 17, "main.arcadia"),
(chain(), 3, 3, 19, 25, 29, "part.arcadia"),
] {
let owner = held(b.clone(), syntax_cap);
let syntax = parse_entry_producers(owner.input()).unwrap();
assert_eq!(owner.retained.entries, c);
assert_eq!(syntax.budget.usage.entries, u);
assert_eq!(syntax.budget.producer_entries(), Some(m));
assert_eq!(syntax.metadata, m);
assert_eq!(
project::text_producer_inventory(&syntax).unwrap().entries,
c + u + m
);
drop(syntax);
let low = held(b.clone(), syntax_cap - 1);
diagnostic(
&failed(parse_entry_producers(low.input())),
refused_path,
0,
1,
);
for cap in [resolve_cap, resolve_cap - 1] {
let owner = held(b.clone(), cap);
let syntax = parse_entry_producers(owner.input()).unwrap();
let usage = syntax.budget.usage;
let numeric = syntax.budget.numeric_units();
match project::resolve(syntax) {
Ok(result) => {
assert_eq!(cap, resolve_cap);
assert_eq!(result.phase_state().1.entries, c + u + m);
assert_eq!(result.phase_state().0.budget.usage, usage);
assert_eq!(result.phase_state().0.budget.numeric_units(), numeric);
let text =
project::resolve(parse_text_producers(owner.input()).unwrap()).unwrap();
assert_eq!(result.project(), text.project());
assert_eq!(result.warnings(), text.warnings());
assert_eq!(result.phase_state().1, text.phase_state().1);
}
Err(failure) => {
assert_eq!(cap, resolve_cap - 1);
assert_eq!(failure.diagnostics.len(), 1);
diagnostic(&failure.diagnostics[0], "main.arcadia", 11, 73);
}
}
assert_eq!(owner.bundle, b);
}
}
}
#[test]
fn owner_and_value_refuse_at_the_original_producer() {
for (body, cap, offset, complete) in [
(
"oa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { }",
4,
75,
29,
),
(
"oa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { description \"\"; }",
7,
149,
37,
),
] {
let owner = held(bundle(body), cap);
diagnostic(
&failed(parse_entry_producers(owner.input())),
"main.arcadia",
offset,
offset + 1,
);
let owner = held(bundle(body), complete);
let syntax = parse_entry_producers(owner.input()).unwrap();
assert_eq!(
owner.retained.entries + syntax.budget.usage.entries + syntax.metadata,
complete
);
// The independent inventory walker has its own existing scratch peak.
let roomy = held(bundle(body), 4096);
let syntax = parse_entry_producers(roomy.input()).unwrap();
assert_eq!(
project::text_producer_inventory(&syntax).unwrap().entries,
complete
);
}
}
const NESTED: &str = "arcadia 8;\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {\noa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { payload { items [@p, uuid \"00000000-0000-0000-0000-000000000001\", { flag true; }]; };\noa_activity b \"B\" id \"00000000-0000-0000-0000-000000000003\" { }\n}\nnode n id \"00000000-0000-0000-0000-000000000004\" { }\n}\n";
fn nested() -> SourceBundle {
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([("main.arcadia".into(), NESTED.into())]),
}
}
#[test]
fn nested_inventory_and_prepaid_projection_quota() {
assert_eq!(NESTED.len(), 344);
let owner = held(nested(), 103);
let syntax = parse_entry_producers(owner.input()).unwrap();
assert_eq!(syntax.budget.usage.entries, 13);
assert_eq!(syntax.budget.producer_entries(), Some(89));
assert_eq!(syntax.metadata, 89);
assert_eq!(
owner.retained.entries + syntax.budget.usage.entries + syntax.metadata,
103
);
let roomy = held(nested(), 4096);
let inventory_syntax = parse_entry_producers(roomy.input()).unwrap();
assert_eq!(
project::text_producer_inventory(&inventory_syntax)
.unwrap()
.entries,
103
);
let file = &syntax.files[0];
assert_eq!(file.tokens.len(), 49);
assert_eq!(file.lexical_owners, [None, Some(0), None]);
assert_eq!(file.positions.len(), 3);
assert_eq!(file.positions[0].1.header.len(), 5);
assert_eq!(file.positions[2].1.header.len(), 4);
let language::EditKind::Record(fields) = &file.positions[0].1.fields.kind else {
panic!("body")
};
let payload = &fields["payload"].value;
assert_eq!(
payload.span,
Span {
start: 145,
end: 221
}
);
let language::EditKind::Record(fields) = &payload.kind else {
panic!("payload")
};
let items = &fields["items"].value;
assert_eq!(
items.span,
Span {
start: 153,
end: 218
}
);
let language::EditKind::List(items) = &items.kind else {
panic!("items")
};
assert_eq!(
items.iter().map(|v| v.span).collect::<Vec<_>>(),
[
Span {
start: 154,
end: 156
},
Span {
start: 158,
end: 201
},
Span {
start: 203,
end: 217
}
]
);
let language::EditKind::Record(fields) = &items[2].kind else {
panic!("member")
};
assert_eq!(
fields["flag"].value.span,
Span {
start: 210,
end: 214
}
);
// Injected prepaid quota errors, not valid alternate source inventories.
for (quota, message) in [
(29, "Source8 projection quota underflow."),
(31, "Source8 projection quota leftover."),
] {
let error = match language::edit_positions_from_tokens_with_producers(
file.source,
&file.ordinary,
|at| lexeme(file.tokens[at]),
file.tokens.len(),
None,
Some(quota),
|_, _| Ok(()),
) {
Err(error) => error,
Ok(_) => panic!("injected projection quota must refuse"),
};
assert_eq!(error.message, message);
}
assert_eq!(syntax.budget.producer_entries(), Some(89));
let low = held(nested(), 102);
diagnostic(
&failed(parse_entry_producers(low.input())),
"main.arcadia",
0,
1,
);
}
fn make_state<'a>(budget: &'a mut Budget, model: &'a mut ModelCounts) -> State<'a> {
State {
source: "",
tokens: &[],
budget,
model,
typed: Vec::new(),
value_ends: BTreeMap::new(),
lexical_owners: Vec::new(),
pending_projection: 0,
reserved_values: 0,
reserved_owners: 0,
}
}
fn internal_budget(cap: usize) -> Budget {
Budget::new(Limits {
entries: cap,
..Limits::source8(ParseLimits::UNLIMITED)
})
.unwrap()
}
#[test]
fn internal_reserve_and_mutate_helpers_leave_collections_unchanged() {
// Scalar fault fixtures exercise atomic mutation; these do not invent real owners.
let mut model = ModelCounts {
limits: ProjectLimits::default(),
graph: 0,
records: 0,
includes: 0,
};
let mut budget = internal_budget(3);
budget.enable_entry_producers(1, 1).unwrap();
let mut state = make_state(&mut budget, &mut model);
state.value_end(Span { start: 10, end: 11 }).unwrap();
state.value_end(Span { start: 10, end: 12 }).unwrap();
assert_eq!(state.value_ends, BTreeMap::from([(10, 12)]));
assert_eq!(state.reserved_values, 1);
assert_eq!(state.budget.producer_entries(), Some(2));
let error = state.value_end(Span { start: 20, end: 21 }).unwrap_err();
assert_eq!(error.message, LIMIT);
assert_eq!(error.offset, 20);
let repeated = state.value_end(Span { start: 10, end: 99 }).unwrap_err();
assert_eq!(repeated.offset, 20);
assert_eq!(repeated.message, LIMIT);
assert_eq!(state.value_ends, BTreeMap::from([(10, 12)]));
assert_eq!(state.budget.producer_entries(), Some(2));
let error = state
.lexical_owner(Some(0), Span { start: 30, end: 31 })
.unwrap_err();
assert_eq!(error.offset, 20); // Original latched refusal survives a later producer.
assert!(state.lexical_owners.is_empty());
assert_eq!(state.reserved_owners, 0);
let mut budget = internal_budget(3);
budget.enable_entry_producers(1, 1).unwrap();
let mut state = make_state(&mut budget, &mut model);
state.lexical_owner(None, Span::default()).unwrap();
assert!(
state
.lexical_owner(None, Span { start: 40, end: 41 })
.is_err()
);
assert_eq!(state.lexical_owners, [None]);
assert_eq!(state.reserved_owners, 1);
assert_eq!(state.budget.producer_entries(), Some(2));
}
#[test]
fn internal_overflow_latch_and_syntax_work_precedence() {
let at = Span { start: 9, end: 10 };
let mut budget = internal_budget(2);
budget.enable_entry_producers(1, 1).unwrap();
let e = budget.entry(at).unwrap_err();
assert_eq!(e.message, LIMIT);
assert_eq!(budget.producer_entry_overflow(Span::default()).span, at);
assert_eq!(budget.active().unwrap_err().message, LIMIT);
assert_eq!(budget.usage.entries, 0);
assert_eq!(budget.producer_entries(), Some(1));
assert_eq!(
budget
.reserve_metadata(0, Span::default())
.unwrap_err()
.span,
at
);
let mut budget = internal_budget(0);
budget.enable_entry_producers(0, 0).unwrap();
assert_eq!(
budget.entry(at).unwrap_err().message,
"Source8 syntax entries limit exceeded."
);
let mut budget = internal_budget(100);
budget.enable_entry_producers(1, 1).unwrap();
assert_eq!(
budget.reserve_metadata(usize::MAX, at).unwrap_err().message,
"Source8 producer entry size overflow."
);
assert_eq!(budget.producer_entries(), Some(1));
assert_eq!(budget.active().unwrap_err().span, at);
let mut budget = internal_budget(100);
assert!(budget.enable_entry_producers(usize::MAX, 1).is_err());
assert_eq!(budget.producer_entries(), None);
assert!(budget.active().is_err());
let mut budget = internal_budget(100);
budget.enable_entry_producers(0, 0).unwrap();
let mut model = ModelCounts {
limits: ProjectLimits::default(),
graph: 0,
records: 0,
includes: 0,
};
let mut state = make_state(&mut budget, &mut model);
state.pending_projection = usize::MAX;
assert_eq!(
state.projection(1, at).unwrap_err().message,
"Source8 producer entry size overflow."
);
assert_eq!(state.pending_projection, usize::MAX);
assert!(state.budget.active().is_err());
}
#[test]
fn explicit_zero_and_actual_owner_preserve_complete_oa_sa_and_old_routes() {
for (work, property) in [
("oa_activity", "oa_property"),
("sa_function", "sa_property"),
] {
let body = format!(
"calculation c \"C\" id \"00000000-0000-0000-0000-000000000003\" {{ input x id \"00000000-0000-0000-0000-000000000004\" : Number; result Number; body $x; }}\n{property} v \"V\" id \"00000000-0000-0000-0000-000000000002\" {{ subject @w; type Number; base @c(x: 1); }}\n{work} w \"W\" id \"00000000-0000-0000-0000-000000000005\" {{}}"
);
let original = bundle(&body);
let owner = held(original.clone(), 4096);
for carried in [false, true] {
let seed = parse_text_producers(owner.input()).unwrap();
let input = if carried {
owner.input()
} else {
CarriedInput::borrowed(&owner.bundle, &seed)
};
let syntax = parse_entry_producers(input).unwrap();
let input = if carried {
owner.input()
} else {
CarriedInput::borrowed(&owner.bundle, &seed)
};
let prior = parse_text_producers(input).unwrap();
assert_eq!(syntax.budget.usage, prior.budget.usage);
assert_eq!(syntax.budget.numeric_units(), prior.budget.numeric_units());
assert_eq!(syntax.metadata, prior.metadata);
assert_eq!(syntax.budget.producer_text(), prior.budget.producer_text());
assert_eq!(syntax.budget.producer_entries(), Some(syntax.metadata));
let result = project::resolve(syntax).unwrap();
let prior = project::resolve(prior).unwrap();
assert_eq!(result.project(), prior.project());
assert_eq!(result.warnings(), prior.warnings());
assert_eq!(result.phase_state().1, prior.phase_state().1);
}
assert_eq!(owner.bundle, original);
}
// Text-only and phase entry acceptance retain their old checkpoints.
for cap in [12, 11] {
let owner = held(bundle(""), cap);
let seed = parse_with_profile(
&owner.bundle,
owner.policy.project_limits,
owner.policy.limits,
)
.unwrap();
assert!(seed.budget.producer_entries().is_none());
let result = parse_entry_producers(CarriedInput::borrowed(&owner.bundle, &seed));
if cap == 12 {
assert_eq!(result.unwrap().metadata, 11);
} else {
diagnostic(&failed(result), "main.arcadia", 0, 1);
}
}
let low = held(
bundle("oa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { }"),
4,
);
diagnostic(
&failed(parse_entry_producers(low.input())),
"main.arcadia",
75,
76,
);
diagnostic(
&failed(parse_text_producers(low.input())),
"main.arcadia",
0,
1,
);
diagnostic(&failed(parse_carried(low.input())), "main.arcadia", 0, 1);
}
@@ -1,4 +1,5 @@
//! Owned output only. No parser, extraction or accepted-result conversion.
//! Owned unverified emission; consumed actual-output verification is a private child.
pub(in crate::language::analysis_source) mod acceptance;
use super::*;
use crate::language::analysis_source::budget::Limits;
use crate::source_project::ProjectLimits;
@@ -0,0 +1,175 @@
//! Actual-byte verification and owning result; no installation or mutable extraction.
use super::*;
use crate::language::analysis_source::budget::Usage;
use crate::language::analysis_source::mixed::{carried, project};
use crate::source_project::SourceDiagnostic;
pub(in crate::language::analysis_source) struct AcceptedRename {
bundle: SourceBundle,
project: Project,
warnings: Vec<SourceDiagnostic>,
limits: Limits,
project_limits: ProjectLimits,
retained: Slots,
verification_usage: Usage,
verification_numeric_units: u64,
}
impl AcceptedRename {
pub fn bundle(&self) -> &SourceBundle {
&self.bundle
}
pub fn project(&self) -> &Project {
&self.project
}
pub fn warnings(&self) -> &[SourceDiagnostic] {
&self.warnings
}
pub fn policy(&self) -> (Limits, ProjectLimits) {
(self.limits, self.project_limits)
}
pub fn retained(&self) -> Slots {
self.retained
}
pub fn verification_work(&self) -> (Usage, u64) {
(self.verification_usage, self.verification_numeric_units)
}
}
impl PendingRename<'_> {
pub(in crate::language::analysis_source::mixed) fn carried_bundle(&self) -> &SourceBundle {
&self.bundle
}
pub(in crate::language::analysis_source::mixed) fn carried_policy(
&self,
) -> (Limits, ProjectLimits) {
(self.limits, self.project_limits)
}
pub(in crate::language::analysis_source::mixed) fn carried_slots(&self) -> Slots {
self.retained
}
pub fn verify(self) -> Result<AcceptedRename, Failure> {
let (project, warnings, retained, verification_usage, verification_numeric_units) = {
let syntax = carried::parse_entry_producers(carried::CarriedInput::from_pending(&self))
.map_err(|diagnostics| Failure { diagnostics })?;
let mut resolved = project::resolve(syntax)?;
let equal = equal_target(&mut resolved, self.desired)?;
if !equal {
return Err(Failure::typed(
&resolved.syntax,
None,
error(
Span::default(),
"Verified output differs from the complete rename target.",
),
));
}
let retained = warning_inventory(&resolved.warnings)
.and_then(|warnings| self.retained.add(warnings, Span::default()))
.and_then(|retained| {
check_destination(&resolved.syntax.budget, resolved.storage.live, retained)?;
Ok(retained)
})
.map_err(|e| Failure::typed(&resolved.syntax, None, e))?;
let usage = resolved.syntax.budget.usage;
let numeric = resolved.syntax.budget.numeric_units();
let project::ResolvedMixedProject {
syntax,
project,
warnings,
expressions,
occurrences,
static_limits: _,
static_usage: _,
storage: _,
} = resolved;
// The full live ledger remains intact through the checks above.
// Only scalar destination accounting escapes these actual drops.
drop(expressions);
drop(occurrences);
drop(syntax);
(project, warnings, retained, usage, numeric)
};
Ok(AcceptedRename {
bundle: self.bundle,
project,
warnings,
limits: self.limits,
project_limits: self.project_limits,
retained,
verification_usage,
verification_numeric_units,
})
}
}
// Every actual Expr root is lowered into this arena. The old pending expression
// count cannot authenticate parsed topology. Keep this boundary independently
// testable without changing the inherited policy or instrumenting comparison.
fn equal_target(
resolved: &mut project::ResolvedMixedProject<'_>,
desired: &Project,
) -> Result<bool, Failure> {
let frontier = Slots::entries(resolved.expressions.len());
resolved
.storage
.reserve(&resolved.syntax.budget, frontier, Span::default())
.map_err(|e| Failure::typed(&resolved.syntax, None, e))?;
let equal = resolved.project.eq(desired);
resolved.storage.release(frontier);
Ok(equal)
}
fn check_destination(budget: &Budget, live: Slots, retained: Slots) -> Result<(), TypedError> {
let at = Span::default();
if retained.entries > live.entries
|| retained.occurrences > live.occurrences
|| retained.text > live.text
{
return Err(error(
at,
"Accepted inventory exceeds live verification storage.",
));
}
budget.lowering_entries(retained.entries, at)?;
budget.lowering_occurrences(retained.occurrences, at)?;
budget.index_bytes(retained.text, at)
}
fn warning_inventory(warnings: &[SourceDiagnostic]) -> Result<Slots, TypedError> {
let mut inventory = Slots::default();
for SourceDiagnostic {
path,
diagnostic,
related,
} in warnings
{
let language::Diagnostic {
severity,
code,
object_id: _,
field,
line: _,
column: _,
message,
span: _,
} = diagnostic;
if !related.is_empty() || *severity == validation::Severity::Error {
return Err(error(Span::default(), "Unexpected accepted warning shape."));
}
let text = [
path.len(),
code.len(),
field.as_ref().map_or(0, String::len),
message.len(),
]
.into_iter()
.try_fold(0, |n, k| add(n, k, Span::default()))?;
inventory = inventory.add(
Slots {
entries: 1,
occurrences: 0,
text,
},
Span::default(),
)?;
}
Ok(inventory)
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,845 @@
use super::*;
use crate::language::{ParseLimits, analysis_source::mixed::parse_with_profile};
use crate::{Layer, analysis as a};
use project::tests::{id, rich_bundle, rich_model, single};
const MISMATCH: &str = "Verified output differs from the complete rename target.";
const NEXT: &str =
"arcadia 8;\nproject next \"Project\" id \"00000000-0000-0000-0000-000000000001\" {\n\n}\n";
const UNNAMED: &str =
"arcadia 8;\nproject next \"\" id \"00000000-0000-0000-0000-000000000001\" {\n\n}\n";
fn profile(entries: usize, bytes: usize) -> Limits {
Limits {
entries,
bytes,
..Limits::source8(ParseLimits::UNLIMITED)
}
}
fn limited(b: &SourceBundle, limits: Limits, policy: ProjectLimits) -> AuthoredMixedProject<'_> {
project::resolve(parse_with_profile(b, policy, limits).unwrap())
.unwrap()
.index()
.unwrap()
.index_ordinary()
.unwrap()
}
fn authored(b: &SourceBundle) -> AuthoredMixedProject<'_> {
limited(b, profile(100_000, 1_000_000), ProjectLimits::default())
}
fn expected(name: &str) -> Project {
Project {
schema_version: 8,
id: id(1),
alias: "next".into(),
name: name.into(),
description: String::new(),
definitions: BTreeMap::new(),
elements: BTreeMap::new(),
relations: BTreeMap::new(),
diagrams: BTreeMap::new(),
artifact_links: BTreeMap::new(),
traces: BTreeMap::new(),
}
}
fn failure<T>(result: Result<T, Failure>) -> Failure {
match result {
Ok(_) => panic!("unexpected verification success"),
Err(e) => e,
}
}
fn diagnostic(failure: &Failure, message: &str, start: usize, end: usize) {
assert_eq!(failure.diagnostics.len(), 1);
let d = &failure.diagnostics[0];
assert_eq!(d.path, "main.arcadia");
assert_eq!(d.diagnostic.code, "invalid_source");
assert_eq!(d.diagnostic.message, message);
assert_eq!(d.diagnostic.span, Span { start, end });
assert!(d.related.is_empty());
}
fn same_policy(accepted: &AcceptedRename, limits: Limits, policy: ProjectLimits) {
let (a, b) = accepted.policy();
assert_eq!(
[a.bytes, a.tokens, a.entries, a.references, a.frames],
[
limits.bytes,
limits.tokens,
limits.entries,
limits.references,
limits.frames
]
);
assert_eq!(a.numeric_units, limits.numeric_units);
assert_eq!(
[
b.max_files,
b.max_include_depth,
b.max_include_edges,
b.model.max_source_bytes,
b.model.max_graph_objects,
b.model.max_records,
b.model.max_step_references
],
[
policy.max_files,
policy.max_include_depth,
policy.max_include_edges,
policy.model.max_source_bytes,
policy.model.max_graph_objects,
policy.model.max_records,
policy.model.max_step_references
]
);
}
#[test]
fn actual_minimal_bytes_full_model_and_late_decode_boundary() {
for bytes in [152, 151] {
let b = single("");
let before = b.clone();
let limits = profile(17, bytes);
let source = limited(&b, limits, ProjectLimits::default());
assert_eq!(source.indexed.resolved.syntax.budget.usage.bytes, 78);
let desired = expected("Project");
let pending = source
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap();
assert_eq!(pending.bundle.files["main.arcadia"], NEXT);
let result = pending.verify();
if bytes == 152 {
let accepted = result.unwrap();
assert_eq!(
accepted.bundle(),
&SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([("main.arcadia".into(), NEXT.into())])
}
);
assert_eq!(accepted.project(), &expected("Project"));
assert!(accepted.warnings().is_empty());
assert_eq!(
accepted.retained(),
Slots {
entries: 1,
occurrences: 0,
text: 105
}
);
assert_eq!(
accepted.verification_work(),
(
Usage {
bytes: 81,
tokens: 19,
entries: 1,
references: 0,
deepest_frame: 0
},
0
)
);
same_policy(&accepted, limits, ProjectLimits::default());
} else {
let e = failure(result);
diagnostic(&e, "Source8 index/output byte limit exceeded.", 37, 38);
assert_eq!(
(
e.diagnostics[0].diagnostic.line,
e.diagnostics[0].diagnostic.column
),
(2, 27)
);
}
assert_eq!(b, before);
assert_eq!(desired, expected("Project"));
}
}
fn warning() -> SourceDiagnostic {
SourceDiagnostic {
path: "main.arcadia".into(),
related: Vec::new(),
diagnostic: language::Diagnostic {
severity: validation::Severity::Warning,
code: "unnamed".into(),
object_id: Some(id(1)),
field: Some("name".into()),
message: "Give the project a descriptive name.".into(),
span: Span { start: 11, end: 69 },
line: 2,
column: 1,
},
}
}
#[test]
fn actual_warning_mapping_boundary_and_final_transfer() {
for bytes in [161, 160] {
let mut b = single("");
b.files.insert(
"main.arcadia".into(),
"arcadia 8;\nproject p \"\" id \"00000000-0000-0000-0000-000000000001\" {\n\n}\n"
.into(),
);
assert_eq!(b.files["main.arcadia"].len(), 71);
let limits = profile(18, bytes);
let source = limited(&b, limits, ProjectLimits::default());
assert_eq!(
source.warnings()[0].diagnostic.span,
Span { start: 11, end: 66 }
);
let desired = expected("");
let pending = source
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap();
let result = pending.verify();
if bytes == 161 {
let accepted = result.unwrap();
assert_eq!(accepted.bundle().files["main.arcadia"], UNNAMED);
assert_eq!(accepted.project(), &expected(""));
assert_eq!(accepted.warnings(), [warning()]);
assert_eq!(
accepted.retained(),
Slots {
entries: 2,
occurrences: 0,
text: 157
}
);
assert_eq!(
accepted.verification_work(),
(
Usage {
bytes: 74,
tokens: 19,
entries: 1,
references: 0,
deepest_frame: 0
},
0
)
);
same_policy(&accepted, limits, ProjectLimits::default());
} else {
let e = failure(result);
diagnostic(&e, "Source8 index/output byte limit exceeded.", 11, 69);
assert_eq!(
(
e.diagnostics[0].diagnostic.line,
e.diagnostics[0].diagnostic.column
),
(2, 1)
);
}
}
}
#[test]
fn owning_result_survives_all_inputs_and_preserves_every_policy_field() {
let limits = Limits {
bytes: 2048,
tokens: 101,
entries: 99,
references: 31,
frames: 17,
numeric_units: 8192,
};
let policy = ProjectLimits {
max_files: 7,
max_include_depth: 3,
max_include_edges: 5,
model: ParseLimits {
max_source_bytes: 3000,
max_graph_objects: 67,
max_records: 123,
max_step_references: 37,
},
};
let accepted = {
let b = single("");
let desired = expected("Project");
let pending = limited(&b, limits, policy)
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap();
// A short pending borrow carries a longer independently owned target borrow.
{
let syntax =
carried::parse_entry_producers(carried::CarriedInput::from_pending(&pending))
.unwrap();
assert_eq!(syntax.bundle, &pending.bundle);
drop(syntax);
}
pending.verify().unwrap()
};
assert_eq!(accepted.bundle().files["main.arcadia"], NEXT);
assert_eq!(accepted.project(), &expected("Project"));
assert!(accepted.warnings().is_empty());
same_policy(&accepted, limits, policy);
}
#[test]
fn equal_byte_wrong_models_malformed_and_admission_failure_are_distinct() {
let b = single("");
let desired = expected("Project");
for (start, end, replacement) in [(19, 23, "nope"), (25, 32, "Changed"), (8, 9, "?")] {
let mut pending = authored(&b)
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap();
pending
.bundle
.files
.get_mut("main.arcadia")
.unwrap()
.replace_range(start..end, replacement);
assert_eq!(pending.bundle.files["main.arcadia"].len(), 81);
if start != 8 {
let actual = project::resolve(
carried::parse_entry_producers(carried::CarriedInput::from_pending(&pending))
.unwrap(),
)
.unwrap();
assert_ne!(actual.project(), &desired); // Wrong output really is admissible.
drop(actual);
diagnostic(&failure(pending.verify()), MISMATCH, 0, 0);
} else {
let e = failure(pending.verify());
assert_eq!(e.diagnostics[0].diagnostic.message, "Invalid source token.");
}
}
let b = single(&format!(
"oa_activity a \"A\" id \"{}\" {{ parent @b; }}\noa_activity b \"B\" id \"{}\" {{}}",
id(2),
id(3)
));
let source = authored(&b);
let mut desired = source.project().clone();
desired.alias = "next".into();
let mut pending = source
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap();
let text = pending.bundle.files.get_mut("main.arcadia").unwrap();
let at = text.find("parent @b").unwrap() + 8;
text.replace_range(at..at + 1, "a");
let syntax =
carried::parse_entry_producers(carried::CarriedInput::from_pending(&pending)).unwrap();
drop(syntax); // This corruption is grammar-valid but model-invalid.
let e = failure(pending.verify());
assert!(
e.diagnostics
.iter()
.any(|d| d.diagnostic.code == "containment_cycle")
);
}
#[test]
fn actual_expression_arena_controls_equality_reservation() {
let b = single(&format!(
"calculation c \"C\" id \"{}\" {{ result Number; body 10000; }}",
id(2)
));
let source = limited(&b, profile(4096, 4096), ProjectLimits::default());
let mut desired = source.project().clone();
desired.alias = "next".into();
let mut pending = source
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap();
assert_eq!(pending.expression_nodes, 1);
assert_eq!(
pending.retained,
Slots {
entries: 1,
occurrences: 0,
text: 195
}
);
pending
.bundle
.files
.get_mut("main.arcadia")
.unwrap()
.replace_range(160..165, "1 + 2");
let mut actual = project::resolve(
carried::parse_entry_producers(carried::CarriedInput::from_pending(&pending)).unwrap(),
)
.unwrap();
assert_eq!(actual.expressions.len(), 3);
let before = actual.storage.live;
assert!(!equal_target(&mut actual, &desired).unwrap());
assert_eq!(actual.storage.live, before);
// Labeled ledger fault: two free slots would fit old E1, but not actual E3.
// This is not an end-to-end late cap or a policy mutation.
actual.storage.live.entries = 4094;
let e = equal_target(&mut actual, &desired).unwrap_err();
diagnostic(&e, "Source8 lowering entry limit exceeded.", 0, 0);
assert_eq!(actual.storage.live.entries, 4094);
drop(actual);
diagnostic(&failure(pending.verify()), MISMATCH, 0, 0);
}
#[test]
fn internal_warning_shape_and_destination_inventory_faults() {
assert_eq!(
warning_inventory(&[warning()]).unwrap(),
Slots {
entries: 1,
occurrences: 0,
text: 59
}
);
let mut bad = warning();
bad.related.push(crate::source_project::SourceLocation {
path: "extra".into(),
span: Span::default(),
});
assert_eq!(
warning_inventory(&[bad]).unwrap_err().message,
"Unexpected accepted warning shape."
);
let mut bad = warning();
bad.diagnostic.severity = validation::Severity::Error;
assert!(warning_inventory(&[bad]).is_err());
let budget = Budget::new(profile(100, 4096)).unwrap();
let live = Slots {
entries: 3,
occurrences: 2,
text: 200,
};
for retained in [
Slots { entries: 4, ..live },
Slots {
occurrences: 3,
..live
},
Slots { text: 201, ..live },
] {
assert_eq!(
check_destination(&budget, live, retained)
.unwrap_err()
.message,
"Accepted inventory exceeds live verification storage."
);
}
assert!(
Slots::text(usize::MAX)
.add(Slots::text(1), Span::default())
.is_err()
);
assert!(
Slots::entries(usize::MAX)
.add(Slots::entries(1), Span::default())
.is_err()
);
}
fn rich_desired(layer: Layer) -> Project {
let mut p = rich_model(layer);
p.alias = "w".into();
p.elements.get_mut(&id(2)).unwrap().alias = "p".into();
p.elements.get_mut(&id(3)).unwrap().alias = "n".into();
let d = p.diagrams.get_mut(&id(5)).unwrap();
d.alias = "z".into();
d.nodes.get_mut(&id(4)).unwrap().alias = "v".into();
for (n, name) in [(10, "g"), (11, "f")] {
let def = p.definitions.get_mut(&id(n)).unwrap();
def.alias = name.into();
let crate::analysis::DefinitionBody::Calculation(c) = &mut def.body else {
panic!("calculation")
};
c.parameters[0].name = "y".into();
c.parameters[1].name = "x".into();
}
if layer == Layer::Sa {
p.elements.get_mut(&id(6)).unwrap().alias = "u".into();
}
p
}
// Independently authored entire expected files: no edit iterator or output text
// contributes to these strings. The LF/CRLF transform applies to whole literals.
const RICH_A: &str = "calculation g \"F\" id \"00000000-0000-0000-0000-000000000010\" {\ninput y id \"00000000-0000-0000-0000-000000000001\" : Number;\ninput x id \"00000000-0000-0000-0000-000000000012\" : Number;\nresult Number; body $y + $x;\n}\n";
const RICH_B: &str = "calculation f \"G\" id \"00000000-0000-0000-0000-000000000011\" {\ninput y id \"00000000-0000-0000-0000-000000000001\" : Number;\ninput x id \"00000000-0000-0000-0000-000000000012\" : Number;\nresult Number; body $y - $x;\n}\n";
fn rich_expected(layer: Layer) -> SourceBundle {
let (property, work, diagram, extra) = if layer == Layer::Oa {
("oa_property", "oa_activity", "", "")
} else {
(
"sa_property",
"sa_function",
"layer Sa;",
"sa_system u \"System\" id \"00000000-0000-0000-0000-000000000006\" {}",
)
};
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([
("a.arcadia".into(), RICH_A.into()),
("b.arcadia".into(), RICH_B.into()),
(
"main.arcadia".into(),
format!(
"arcadia 8;\nproject w \"Project\" id \"00000000-0000-0000-0000-000000000001\" {{\ninclude \"b.arcadia\"; include \"a.arcadia\";\nnode v id \"00000000-0000-0000-0000-000000000004\" {{ diagram @z; element @n; }}\n{property} n \"Value\" id \"00000000-0000-0000-0000-000000000003\" {{ subject @p; type Number; base @g(x: 2, y: @f(y: 3, x: 4)) + @g(y: 5, x: 6); }}\n{work} p \"Work\" id \"00000000-0000-0000-0000-000000000002\" {{}}\ndiagram z \"Diagram\" id \"00000000-0000-0000-0000-000000000005\" {{ {diagram} }}\n{extra}\n}}\n"
),
),
]),
}
}
#[test]
fn rich_oa_sa_swaps_unicode_crlf_complete_actual_bytes_and_models() {
for (layer, bytes, unicode_bytes) in [(Layer::Oa, 900, 943), (Layer::Sa, 974, 1017)] {
for unicode in [false, true] {
let mut b = rich_bundle(layer);
let mut expected = rich_expected(layer);
if unicode {
for text in b.files.values_mut() {
*text = format!("// 雨\r\n{}", text.replace('\n', "\r\n"));
}
for text in expected.files.values_mut() {
*text = format!("// 雨\r\n{}", text.replace('\n', "\r\n"));
}
}
let before = b.clone();
let desired = rich_desired(layer);
let accepted = authored(&b)
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap()
.verify()
.unwrap();
assert_eq!(accepted.bundle(), &expected);
assert_eq!(accepted.project(), &rich_desired(layer));
let (span, line) = if unicode {
(
Span {
start: 351,
end: 416,
},
7,
)
} else {
(
Span {
start: 338,
end: 403,
},
6,
)
};
assert_eq!(
accepted.warnings(),
leaf_work_warnings("main.arcadia", span, line)
);
assert_eq!(
accepted.retained(),
Slots {
entries: 5,
occurrences: 0,
text: if unicode {
unicode_bytes + 42 + 227
} else {
bytes + 42 + 227
}
}
);
assert_eq!(b, before);
assert_eq!(desired, rich_desired(layer));
}
}
}
fn leaf_work_warnings(path: &str, span: Span, line: usize) -> [SourceDiagnostic; 2] {
// Allocation validation precedes advisory coverage. Both locate the complete
// ordinary Work declaration; neither fixture supplies allocation or context.
[
("unallocated_leaf", Some("allocation"), "Allocate leaf 'Work' to a performer."),
("work_without_behavior_context", None, "Include this leaf activity/function in a process/chain or scenario through a work action or exchange occurrence."),
].map(|(code, field, message)| SourceDiagnostic {
path: path.into(), related: Vec::new(),
diagnostic: language::Diagnostic {
severity: validation::Severity::Warning, code: code.into(),
object_id: Some(id(2)), field: field.map(str::to_owned),
message: message.into(), span, line, column: 1,
},
})
}
fn calculation(p: &mut Project) -> &mut a::CalculationDefinition {
let a::DefinitionBody::Calculation(c) = &mut p.definitions.get_mut(&id(2)).unwrap().body else {
panic!("calculation")
};
c
}
#[test]
fn scoped_small_stack_actual_deep_wide_verification_and_drop() {
for (expression, result, nodes) in [
(
format!("{}1{}", "abs(".repeat(127), ")".repeat(127)),
"Number",
128,
),
(
format!("[{}]", vec!["1"; 128].join(", ")),
"List(Number)",
129,
),
] {
let body = format!(
"calculation c \"C\" id \"{}\" {{ result {result}; body {expression}; }}",
id(2)
);
let b = single(&body);
let before = b.clone();
let expected_bytes = format!(
"arcadia 8;\nproject next \"Project\" id \"00000000-0000-0000-0000-000000000001\" {{\n{body}\n}}\n"
);
for mismatch in [false, true] {
let source = authored(&b);
let mut desired = source.project().clone();
desired.alias = "next".into();
let mut target = desired.clone();
if result == "List(Number)" {
let a::Binding::Expression(a::Expr::List(items)) =
&mut calculation(&mut target).body
else {
panic!("list")
};
items.pop(); // Actual left remains wide; target-only shape differs.
} else {
target.name = "Different".into();
}
let mut pending = source
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap();
assert_eq!(pending.expression_nodes, nodes);
if mismatch {
pending.desired = &target;
} // Labeled target-only fault after valid emission.
std::thread::scope(|scope| {
let expected = &expected_bytes;
let desired = &desired;
std::thread::Builder::new()
.stack_size(128 * 1024)
.spawn_scoped(scope, move || {
let result = pending.verify();
if mismatch {
diagnostic(&failure(result), MISMATCH, 0, 0);
} else {
let accepted = result.unwrap();
assert_eq!(&accepted.bundle().files["main.arcadia"], expected);
assert_eq!(accepted.project(), desired);
drop(accepted);
}
})
.unwrap()
.join()
.unwrap();
});
assert_eq!(b, before);
}
}
// Genuine inherited151 profile reaches a late parser refusal inside worker.
let b = single("");
let desired = expected("Project");
let pending = limited(&b, profile(17, 151), ProjectLimits::default())
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap();
std::thread::scope(|scope| {
std::thread::Builder::new()
.stack_size(128 * 1024)
.spawn_scoped(scope, move || {
diagnostic(
&failure(pending.verify()),
"Source8 index/output byte limit exceeded.",
37,
38,
);
})
.unwrap()
.join()
.unwrap();
});
}
#[test]
fn zero_actual_expression_rejects_large_target_only_variants() {
let b = single(&format!(
"calculation c \"C\" id \"{}\" {{ result Number; body unbound; }}",
id(2)
));
let mut deep = a::Expr::Boolean(true);
for _ in 0..2048 {
deep = a::Expr::Unary {
op: a::UnaryOp::Not,
operand: Box::new(deep),
};
}
for expression in [deep, a::Expr::List(vec![a::Expr::Boolean(true); 4096])] {
let source = authored(&b);
let mut desired = source.project().clone();
desired.alias = "next".into();
let mut damaged = desired.clone();
calculation(&mut damaged).body = a::Binding::Expression(expression);
let mut pending = source
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap();
assert_eq!(pending.expression_nodes, 0);
pending.desired = &damaged;
diagnostic(&failure(pending.verify()), MISMATCH, 0, 0);
}
}
#[test]
fn signed_zero_geometry_scalar_and_historical_equality_moves_actual_model() {
let b = rich_bundle(Layer::Oa);
let mut desired = rich_model(Layer::Oa);
desired.alias = "next".into();
desired
.diagrams
.get_mut(&id(5))
.unwrap()
.nodes
.get_mut(&id(4))
.unwrap()
.position
.x = -0.0;
let accepted = authored(&b)
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap()
.verify()
.unwrap();
assert_eq!(accepted.project(), &desired);
assert_eq!(
accepted.project().diagrams[&id(5)].nodes[&id(4)]
.position
.x
.to_bits(),
0.0f32.to_bits()
);
let b = single(&format!(
r#"
oa_data_type a "A" id "{}" {{ definition {{ Real {{ minimum 0.0; maximum 1.0; }}; }}; default {{ Real 0.0; }}; }}
sa_data_type b "B" id "{}" {{ definition {{ Real {{ minimum 0.0; maximum 1.0; }}; }}; default {{ Real 0.0; }}; }}
trace t "T" id "{}" {{ oa @a; sa @b; kind Item; baseline {{
oa {{ name "A"; description ""; }}; sa {{ name "B"; description ""; }};
semantic {{ facets [{{ Data {{ RealMinimum {{ oa {{ Value 0.0; }}; sa {{ Value 0.0; }}; }}; }}; }}]; }};
}}; }}
"#,
id(2),
id(3),
id(4)
));
let source = authored(&b);
let mut desired = source.project().clone();
desired.alias = "next".into();
for e in desired.elements.values_mut() {
let body = match &mut e.body {
crate::ElementBody::Oa(crate::OaBody::Shared(b))
| crate::ElementBody::Sa(crate::SaBody::Shared(b)) => b,
_ => panic!("data"),
};
let crate::SharedBody::DataType {
definition,
default,
..
} = body
else {
panic!("data")
};
let crate::DataTypeDefinition::Real { minimum, .. } = definition else {
panic!("real")
};
*minimum = Some(-0.0);
*default = Some(crate::DataValue::Real(-0.0));
}
let facet = &mut desired
.traces
.get_mut(&id(4))
.unwrap()
.baseline
.as_mut()
.unwrap()
.semantic
.as_mut()
.unwrap()
.facets[0];
let crate::semantic::FacetReview::Data(crate::semantic::DataReview::RealMinimum(review)) =
facet
else {
panic!("historical")
};
review.oa = crate::semantic::Observed::Value(Some(-0.0));
review.sa = crate::semantic::Observed::Value(Some(-0.0));
let accepted = source
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&b)
.unwrap()
.verify()
.unwrap();
assert_eq!(accepted.project(), &desired);
for e in accepted.project().elements.values() {
let body = match &e.body {
crate::ElementBody::Oa(crate::OaBody::Shared(b))
| crate::ElementBody::Sa(crate::SaBody::Shared(b)) => b,
_ => panic!("data"),
};
let crate::SharedBody::DataType {
definition:
crate::DataTypeDefinition::Real {
minimum: Some(minimum),
..
},
default: Some(crate::DataValue::Real(value)),
..
} = body
else {
panic!("real")
};
assert_eq!(minimum.to_bits(), 0.0f64.to_bits());
assert_eq!(value.to_bits(), 0.0f64.to_bits());
}
}
#[test]
fn four_file_cross_family_swap_preserves_entire_untouched_file_and_comments() {
let bundle=SourceBundle {entry:"main.arcadia".into(),files:BTreeMap::from([
("a.arcadia".into(),"calculation f \"F\" id \"00000000-0000-0000-0000-000000000010\" { result Number; body 1; }\r\n".into()),
("b.arcadia".into(),"// f and w are comments\r\noa_activity w \"Work\" id \"00000000-0000-0000-0000-000000000002\" {}\r\n".into()),
("main.arcadia".into(),"// 雨\r\narcadia 8;\r\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {\r\ninclude \"a.arcadia\"; include \"b.arcadia\"; include \"untouched.arcadia\";\r\noa_property v \"Value\" id \"00000000-0000-0000-0000-000000000003\" { subject @w; type Number; base @f(); }\r\n}\r\n".into()),
("untouched.arcadia".into(),"// 雪 untouched f w\r\n".into()),
])};
let expected=SourceBundle {entry:"main.arcadia".into(),files:BTreeMap::from([
("a.arcadia".into(),"calculation w \"F\" id \"00000000-0000-0000-0000-000000000010\" { result Number; body 1; }\r\n".into()),
("b.arcadia".into(),"// f and w are comments\r\noa_activity f \"Work\" id \"00000000-0000-0000-0000-000000000002\" {}\r\n".into()),
("main.arcadia".into(),"// 雨\r\narcadia 8;\r\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {\r\ninclude \"a.arcadia\"; include \"b.arcadia\"; include \"untouched.arcadia\";\r\noa_property v \"Value\" id \"00000000-0000-0000-0000-000000000003\" { subject @f; type Number; base @w(); }\r\n}\r\n".into()),
("untouched.arcadia".into(),"// 雪 untouched f w\r\n".into()),
])};
let source = authored(&bundle);
let mut desired = source.project().clone();
desired.definitions.get_mut(&id(10)).unwrap().alias = "w".into();
desired.elements.get_mut(&id(2)).unwrap().alias = "f".into();
let pending = source
.plan_rename(&desired)
.unwrap()
.emit_changed_unverified(&bundle)
.unwrap();
let pending = pending.verify().unwrap();
assert_eq!(pending.project(), &desired);
assert_eq!(
pending.warnings(),
leaf_work_warnings("b.arcadia", Span { start: 25, end: 90 }, 2)
);
assert_eq!(pending.bundle, expected);
assert_eq!(
pending.retained,
Slots {
entries: 6,
occurrences: 0,
text: 526 + 221
}
);
}
@@ -522,8 +522,18 @@ fn partial_wide_and_deep_input_cleanup_does_not_poison_a_fresh_parse() {
}
#[test]
fn public_source_headers_and_existing_numeric_token_controls_are_unchanged() {
assert!(crate::language::parse("arcadia 8; project p \"P\" {}").is_err());
fn public_ordinary_source8_preserves_typed_refusal_and_numeric_controls() {
let ordinary = crate::language::parse_with_report("arcadia 8; project p \"P\" {}").unwrap();
assert!(ordinary.migration.is_none());
assert_eq!(ordinary.project.schema_version, 8);
assert_eq!(ordinary.project.alias, "p");
assert_eq!(ordinary.project.name, "P");
assert!(
crate::language::parse(
"arcadia 8; project p \"P\" { calculation c \"C\" { result Number; body 1; } }"
)
.is_err()
);
for accepted in ["-1.5", "1e-2", "0", "-0"] {
let tokens = crate::language::tokenize(accepted);
assert_eq!(tokens.len(), 1);
@@ -133,6 +133,16 @@ fn typed_oa_sa_canonical_roundtrips_match_independent_projects() {
);
}
let text = canonical(&expected);
let ordinary8 = ordinary_source.replacen("arcadia 7", "arcadia 8", 1);
let ordinary_decoded = crate::language::parse_with_report(&ordinary8).unwrap();
assert!(ordinary_decoded.migration.is_none());
assert_eq!(
ordinary_decoded.project,
crate::language::parse(ordinary_source).unwrap()
);
assert!(
crate::language::parse(&format!("arcadia 8; project p \"P\" {{ {text} }}")).is_err()
);
let actual = parse_typed(crate::language::parse(ordinary_source).unwrap(), &text);
assert_eq!(actual, expected);
a::admit(&actual, a::AdmissionLimits::REFERENCE).unwrap();
@@ -147,7 +157,6 @@ fn typed_oa_sa_canonical_roundtrips_match_independent_projects() {
assert!(text.contains(&format!("{prefix}_{word} ")));
}
}
assert!(crate::language::parse("arcadia 8; project p \"P\" {}").is_err());
}
#[test]
@@ -0,0 +1,353 @@
//! Ordinary current-schema compatibility; typed analysis authoring remains private.
use super::*;
use crate::source_project::{self, ProjectLimits, SourceBundle, compile_bundle};
fn example_bundle() -> SourceBundle {
SourceBundle {
entry: "project.arcadia".into(),
files: [
(
"project.arcadia",
include_str!("../../../examples/source-project/project.arcadia"),
),
(
"oa.arcadia",
include_str!("../../../examples/source-project/oa.arcadia"),
),
(
"sa/participants.arcadia",
include_str!("../../../examples/source-project/sa/participants.arcadia"),
),
(
"sa/reporting.arcadia",
include_str!("../../../examples/source-project/sa/reporting.arcadia"),
),
(
"sa/assurance.arcadia",
include_str!("../../../examples/source-project/sa/assurance.arcadia"),
),
(
"views.arcadia",
include_str!("../../../examples/source-project/views.arcadia"),
),
]
.into_iter()
.map(|(path, text)| (path.into(), text.into()))
.collect(),
}
}
fn document(version: &str, body: &str) -> String {
format!(
"arcadia {version};\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {{\n{body}\n}}\n"
)
}
fn identity(project: &Project, alias: &str) -> Id {
project
.identities()
.into_iter()
.find(|(_, name)| *name == alias)
.unwrap()
.0
}
#[test]
fn current_formatter_roundtrips_complete_ordinary_examples_and_history() {
let input = example_bundle();
let before = input.clone();
let compiled = compile_bundle(&input, ProjectLimits::default()).unwrap();
let mut example = compiled.decoded.project;
assert!(example.elements.values().any(|e| e.layer() == Layer::Oa));
assert!(example.elements.values().any(|e| e.layer() == Layer::Sa));
assert!(!example.diagrams.is_empty());
// The published example has no traces: author an explicit participant link
// rather than assuming that the example already exercises this record class.
let trace_id = Id::from_u128(0x188b_0000_0000_0000_0000_0000_0000_0001);
let trace = Trace {
id: trace_id,
alias: "rescue_participant_trace".into(),
name: "Coordination participant realization".into(),
description: "Relates the operational coordination team to its selected system.".into(),
oa: Id::parse_str("00000000-0000-4000-8000-000000000102").unwrap(),
sa: Id::parse_str("00000000-0000-4000-8000-000000000301").unwrap(),
kind: TraceKind::Participant,
baseline: None,
};
assert_eq!(example.elements[&trace.oa].kind(), Kind::Entity);
assert_eq!(example.elements[&trace.sa].kind(), Kind::System);
assert!(example.traces.insert(trace_id, trace.clone()).is_none());
assert_eq!(example.traces[&trace_id], trace);
let historical =
crate::persistence::decode(include_bytes!("../tests/fixtures/v4-reviewed-prose.json"))
.unwrap();
assert!(historical.traces.values().any(|t| t.baseline.is_some()));
for project in [example, historical] {
assert!(project.definitions.is_empty());
let source = format(&project);
assert!(source.starts_with("arcadia 8;"));
let decoded = parse_with_report(&source).unwrap();
assert_eq!(decoded.project, project);
assert!(decoded.migration.is_none());
let bundle = source_project::format_bundle(&project);
let compiled = compile_bundle(&bundle, ProjectLimits::default()).unwrap();
assert_eq!(compiled.bundle, bundle);
assert_eq!(compiled.decoded.project, project);
assert!(compiled.decoded.migration.is_none());
let again = compile_bundle(&compiled.bundle, ProjectLimits::default()).unwrap();
assert_eq!(again.bundle, compiled.bundle);
assert_eq!(again.index, compiled.index);
assert_eq!(again.decoded.project, project);
assert_eq!(again.diagnostics, compiled.diagnostics);
assert!(again.decoded.migration.is_none());
}
assert_eq!(input, before);
}
#[test]
fn versions_three_through_seven_migrate_to_idempotent_ordinary_eight() {
for version in ["3", "4", "5", "6", "7"] {
let source = document(version, "");
let decoded = parse_with_report(&source).unwrap();
assert_eq!(decoded.project.schema_version, 8);
assert!(decoded.migration.is_some());
let input = SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([("project.arcadia".into(), source.clone())]),
};
let compiled = compile_bundle(&input, ProjectLimits::default()).unwrap();
assert_eq!(compiled.decoded.project, decoded.project);
assert!(compiled.decoded.migration.is_some());
assert!(compiled.bundle.files["project.arcadia"].starts_with("arcadia 8;"));
let again = compile_bundle(&compiled.bundle, ProjectLimits::default()).unwrap();
assert!(again.decoded.migration.is_none());
assert_eq!(again.decoded.project, decoded.project);
assert_eq!(again.bundle, compiled.bundle);
assert_eq!(again.index, compiled.index);
assert_eq!(input.files["project.arcadia"], source);
}
}
#[test]
fn included_nested_omitted_ids_keep_actual_index_and_unicode_crlf() {
let entry =
"// 海岸\r\narcadia 8;\r\nproject coast \"Coast\" { include \"team.arcadia\"; }\r\n";
let fragment = "// Keep nested ownership é\r\noa_entity team \"Team\" {\r\n oa_actor sailor \"Sailor\" { description \"海\"; }\r\n}\r\n";
let input = SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([
("project.arcadia".into(), entry.into()),
("team.arcadia".into(), fragment.into()),
]),
};
let before = input.clone();
let compiled = compile_bundle(&input, ProjectLimits::default()).unwrap();
assert!(compiled.decoded.migration.is_none());
let p = &compiled.decoded.project;
let team = identity(p, "team");
let sailor = identity(p, "sailor");
assert_eq!(p.elements[&sailor].parent, Some(team));
assert_eq!(p.elements[&sailor].description, "海");
assert_eq!(compiled.index.lexical_owners[&sailor], team);
for (id, prefix) in [
(team, "oa_entity team \"Team\""),
(sailor, "oa_actor sailor \"Sailor\""),
] {
let location = &compiled.index.declarations[&id];
assert_eq!(location.path, "team.arcadia");
let actual = &compiled.bundle.files[&location.path][location.span.start..location.span.end];
assert!(actual.starts_with(prefix));
assert!(actual.contains(&format!("id \"{id}\"")));
assert!(actual.ends_with('}'));
}
assert!(compiled.bundle.files["project.arcadia"].starts_with("// 海岸\r\narcadia 8;\r\n"));
assert!(compiled.bundle.files["team.arcadia"].starts_with("// Keep nested ownership é\r\n"));
for text in compiled.bundle.files.values() {
assert_eq!(text.matches('\n').count(), text.matches("\r\n").count());
}
assert!(compiled.diagnostics.is_empty());
let again = compile_bundle(&compiled.bundle, ProjectLimits::default()).unwrap();
assert_eq!(again.bundle, compiled.bundle);
assert_eq!(again.index, compiled.index);
assert_eq!(again.decoded.project, *p);
let mut stale = again.clone();
stale.decoded.project.name = "stale".into();
let error = source_project::update_bundle(&stale, p, ProjectLimits::default()).unwrap_err();
assert_eq!(error[0].diagnostic.code, "stale_source_index");
assert_eq!(input, before);
}
#[test]
fn typed_analysis_future_headers_and_limits_still_refuse() {
for word in [
"quantity_kind",
"unit",
"calculation",
"oa_property",
"sa_property",
"oa_check",
"sa_check",
"oa_analysis",
"oa_case",
"sa_analysis",
"sa_case",
"oa_engineering_property",
"sa_engineering_property",
"oa_analysis_check",
"sa_analysis_check",
"oa_analysis_case",
"sa_analysis_case",
] {
let fragment = format!("{word} x \"X\" id \"00000000-0000-0000-0000-000000000002\" {{}}\n");
let source = document("8", &fragment);
assert!(parse(&source).is_err(), "{word}");
let input = SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([
(
"project.arcadia".into(),
document("8", "include \"part.arcadia\";"),
),
("part.arcadia".into(), fragment.clone()),
]),
};
let error = compile_bundle(&input, ProjectLimits::default()).unwrap_err();
assert_eq!(error[0].path, "part.arcadia", "{word}");
assert_eq!(error[0].diagnostic.code, "invalid_source");
assert_eq!(error[0].diagnostic.span.start, 0);
if word.contains("engineering_property")
|| word.contains("analysis_check")
|| word.contains("analysis_case")
{
assert_eq!(
error[0].diagnostic.message,
"Analysis records require the source8 authoring implementation."
);
}
assert_eq!(input.files["part.arcadia"], fragment);
}
for version in ["0", "9", "99", "8.0"] {
let error = parse(&document(version, "")).unwrap_err();
assert_eq!(
error[0].message,
"Unsupported schema version: expected arcadia 3, 4, 5, 6, 7 or 8."
);
assert_eq!(error[0].span.start, 8);
}
let source = document("8", "");
let error = parse_limited(
&source,
ParseLimits {
max_source_bytes: source.len() - 1,
..ParseLimits::UNLIMITED
},
)
.unwrap_err();
assert_eq!(error[0].code, "source_limit");
let error = parse_limited(
&source,
ParseLimits {
max_records: 0,
..ParseLimits::UNLIMITED
},
)
.unwrap_err();
assert_eq!(error[0].code, "model_limit");
assert!(parse("arcadia 8; project").is_err());
}
#[test]
fn public_checked_save_reopens_ordinary_source_and_json_but_refuses_lost_definition() {
let project = parse_with_report(include_str!("../../../examples/text-first/project.arcadia"))
.unwrap()
.project;
let dir = tempfile::tempdir().unwrap();
for name in ["ordinary.arcadia", "ordinary.json"] {
let path = dir.path().join(name);
let bytes = crate::persistence::save_checked(&path, &project, None).unwrap();
assert_eq!(std::fs::read(&path).unwrap(), bytes);
let decoded = crate::persistence::decode_with_report(&bytes).unwrap();
assert_eq!(decoded.project, project);
assert!(decoded.migration.is_none());
assert_eq!(
crate::persistence::save_checked(&path, &project, Some(&bytes)).unwrap(),
bytes
);
}
let mut with_definition = Project::empty();
let id = Id::parse_str("188b911c-5f8b-431c-9eea-2d41f3953767").unwrap();
with_definition.definitions.insert(
id,
crate::analysis::Definition {
id,
alias: "duration".into(),
name: "Duration".into(),
description: String::new(),
body: crate::analysis::DefinitionBody::QuantityKind(
crate::analysis::QuantityKindDefinition {
dimension: crate::analysis_numeric::Dimension::new([0, 0, 1, 0, 0, 0, 0])
.unwrap(),
algebra: crate::analysis::QuantityAlgebra::Linear,
},
),
},
);
let path = dir.path().join("protected.arcadia");
let sentinel = b"external unfinished draft\r\n";
std::fs::write(&path, sentinel).unwrap();
let count = std::fs::read_dir(dir.path()).unwrap().count();
let error =
crate::persistence::save_checked(&path, &with_definition, Some(sentinel)).unwrap_err();
assert!(
matches!(error,crate::persistence::FileError::Invalid(message) if message=="Generated source does not reproduce the complete requested project.")
);
assert_eq!(std::fs::read(&path).unwrap(), sentinel);
assert_eq!(std::fs::read_dir(dir.path()).unwrap().count(), count);
}
#[test]
fn cli_preview_literals_compile_to_independent_complete_projects() {
let entry = "// 雨\r\narcadia 8;\r\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {\r\ninclude \"participants.arcadia\"; include \"children.arcadia\";\r\n}\r\n";
let before_team = "// @team remains documentation\r\noa_entity team \"Team @team\" id \"00000000-0000-0000-0000-000000000002\" {}\r\n";
let after_team = "// @team remains documentation\r\noa_entity rescue_team \"Team @team\" id \"00000000-0000-0000-0000-000000000002\" {}\r\n";
let before_child = "oa_entity child \"Child\" id \"00000000-0000-0000-0000-000000000003\" { parent @team; }\r\n";
let after_child = "oa_entity child \"Child\" id \"00000000-0000-0000-0000-000000000003\" { parent @rescue_team; }\r\n";
let project_id = Id::from_u128(1);
let team_id = Id::from_u128(2);
let child_id = Id::from_u128(3);
let mut expected = Project::empty();
expected.id = project_id;
expected.alias = "p".into();
expected.name = "Project".into();
for (id, alias, name, parent) in [
(team_id, "team", "Team @team", None),
(child_id, "child", "Child", Some(team_id)),
] {
expected.elements.insert(
id,
Element {
id,
alias: alias.into(),
name: name.into(),
description: String::new(),
parent,
body: ElementBody::new(Kind::Entity, Layer::Oa).unwrap(),
},
);
}
for (team, child, alias) in [
(before_team, before_child, "team"),
(after_team, after_child, "rescue_team"),
] {
let bundle = SourceBundle {
entry: "project.arcadia".into(),
files: [
("project.arcadia", entry),
("participants.arcadia", team),
("children.arcadia", child),
]
.into_iter()
.map(|(path, source)| (path.into(), source.into()))
.collect(),
};
expected.elements.get_mut(&team_id).unwrap().alias = alias.into();
let compiled = compile_bundle(&bundle, ProjectLimits::default()).unwrap();
assert_eq!(compiled.decoded.project, expected);
assert!(compiled.decoded.migration.is_none());
assert!(compiled.diagnostics.is_empty());
assert_eq!(compiled.bundle, bundle);
}
}
+3
View File
@@ -25,7 +25,10 @@ fn frozen_v4_json_and_source_preserve_the_old_review_without_fabricating_semanti
assert_eq!(baseline.sa.name, "Approved SA name");
assert_ne!(baseline.oa.name, decoded.project.named(trace.oa));
let mut expected: Value = serde_json::from_slice(JSON4).unwrap();
assert!(expected.as_object().unwrap().get("definitions").is_none());
assert!(decoded.project.definitions.is_empty());
expected["schema_version"] = oa_core::SCHEMA_VERSION.into();
expected["definitions"] = serde_json::json!({});
assert_eq!(serde_json::to_value(&decoded.project).unwrap(), expected);
let reopened =
persistence::decode_with_report(&serde_json::to_vec(&decoded.project).unwrap())
+5 -2
View File
@@ -1036,16 +1036,19 @@ fn source7_source_bundle_migration_is_token_only_and_new_link_stays_with_caller(
let f = chain(Layer::Oa);
let mut bundle = source_project::format_bundle(&f.p);
let entry = bundle.files.get_mut(&bundle.entry).unwrap();
assert!(entry.starts_with("arcadia 8;"));
*entry = entry
.replacen("arcadia 7;", "arcadia // preserve this header\r\n 6;", 1)
.replacen("arcadia 8;", "arcadia // preserve this header\r\n 6;", 1)
.replace('\n', "\r\n");
assert!(entry.starts_with("arcadia // preserve this header"));
assert_eq!(entry.matches(" 6;").count(), 1);
let old = bundle.clone();
let compiled = source_project::compile_bundle(&bundle, Default::default()).unwrap();
assert_eq!(compiled.decoded.project, f.p);
assert_eq!(compiled.decoded.migration.as_ref().unwrap().from_schema, 6);
for (path, source) in &old.files {
let expected = if path == &old.entry {
source.replacen(" 6;", " 7;", 1)
source.replacen(" 6;", " 8;", 1)
} else {
source.clone()
};
@@ -0,0 +1,56 @@
# Private retained-entry producer prerequisite
Base `97a8703adc75467a7f8e75f5c6cb00ca542b707c`; implements the accepted
`harness/analysis-entry-producer-contract-20260925.md` in the parent source tree.
This is fixture-only phase/producer evidence, not emitted-output admission.
The explicit entry route also enables text producers and derives C and exact
policy from the existing carried guard. Budget adds only optional scalar C/M.
Existing legacy, carried-phase and text-only entry paths retain their checkpoints.
U keeps its original event sites and work-limit precedence; prospective aggregate
checks latch without changing totals. F is installed before traversal. Private
State helpers reserve unique value-end keys and lexical owner occurrences before
mutation. Successful grammar events count projection slots with checked addition.
The new route reserves token-end slots and projection together, without the old
borrowed-work counting Vec. A scalar quota checks each projected envelope, field
and actual value before growth. Synthetic bodies remain included in seven.
Per-file actual map/owner lengths and final independent metadata reconcile M;
resolver C then U+M transfer and text reconciliation are unchanged.
Six tests in `mixed::carried::entry_producer` cover independent literal E and H
syntax caps 13/12 and 25/24 separately from resolver 17/16 and 29/28, owner/value
prefix refusals 4/7, and the 344-byte nested syntax-only fixture (U13, M89, P30,
C1; 103/102). They check node/element envelopes, nested positions and owner order,
quota underflow/leftover, direct unchanged collections on refusal and repeated
map-key replacement. Labeled arithmetic controls exercise overflow, sticky-first
failure, prospective U and work-limit precedence. Whole OA/SA Project, warnings,
Usage, numeric work and retained totals are compared with text-only under actual
and explicit zero guards. Old-route diagnostics and original bytes are checked.
All byte policies are roomy 4096 so earlier text limits do not mask entry tests.
Source review identified a possible internal overflow call overwriting an already
latched error. The helper now returns the first failure; a direct fault control
checks that behavior. Repeated map keys now perform a zero-delta reservation
before replacement, preserving a latched refusal without modifying the map;
a direct control checks that branch too. These were corrected before compilation,
not a runtime bug
observation. Test review also caught that the independent inventory walker adds
its own existing scratch slots: exact-cap ordinary syntax controls remain at
29/37/103, while a separate roomy owner of the same literal runs that walk.
No live policy is widened and no exact-cap resolver success is implied.
No prior tests, stack sizes, policies, inherited failure dispositions
or unrelated formatting are changed. Only touched Rust files were directly
formatted with skip_children. Parent owns compiler, full disposition, Clippy,
format and prologue/stack acceptance; no runtime result is claimed here.
Allocator overhead, token storage under its existing token ceiling, delimiter and
other work stacks, numeric/decoder internals and model/validator heap remain
outside this retained metric. The extra shared projector wrapper and scalar
Budget/State fields require measured parent stack checks; no heap indirection or
larger stack is introduced. Final output acceptance remains a separate task.
Initial a629ae01 focused compilation failed before any test ran: the quota
control's unwrap_err required Debug on its successful EditDeclaration vector.
The test now matches Err explicitly and panics on unexpected Ok, preserving
the same assertion without changing production types. Original compiler output
and source-bound receipt remain with parent gate evidence.
@@ -0,0 +1,90 @@
# Private actual-output rename acceptance
Base `29417130c71f70c4eaefe8ed4c879efba78eb72c`. Implements the parent-selected
`harness/analysis-mixed-acceptance-contract-20260925.md` (root b86c8dbc) within the
private experimental mixed source path. No public source8, save, history,
installation or repeated carried-source emission is enabled.
A consumed PendingRename now verifies its actual complete bundle through the
combined text/entry producer parser and existing complete resolver/admission.
CarriedInput and its slim guard borrow that entire pending owner; covariance
shortens the desired borrow to the verification scope without another parser
lifetime, policy copy in guards, arbitrary tuple constructor or heap wrapper.
Narrow immutable views supply the exact bundle, policy and retained O together.
The existing fixture constructors and old parser modes remain available unchanged.
The actual resolved expression arena supplies E for equality reservation against
its full live storage. Complete actual Project PartialEq compares every field
with desired; old name-exempt equality and pending's original E are not used.
A static entry/default-span diagnostic reports mismatch. Warning inventory uses
an exhaustive borrowed diagnostic-field match, checks empty related/non-Error
shapes and sums checked entries/text. O+W is checked as a componentwise live
subset and against inherited ceilings without resetting or double-charging the
full ledger. Actual model/warnings move after equality; expression/occurrence
arenas, syntax and complete pending guard drop before the bundle moves into
AcceptedRename. Its private fields have read-only accessors, exact policies,
final retained slots and separate verification Usage/numeric work; no input or
desired borrow remains.
Eleven new tests use the acceptance module filter. Independent minimal literal
and whole model verify 152/151 through one unchanged profile; warning literals
and full diagnostics verify 161/160 and final O+W=(2,0,157). Exact policy fields
and a lexical scope dropping all inputs exercise returned ownership. Previously
authored OA/SA/scoped-global swap and UTF-8/CRLF/all-file literals are reused,
including an untouched fourth file. Equal-byte wrong alias/name, malformed
source and parseable containment-cycle corruption separate equality, syntax and
model-admission refusal. Actual E1->3 corruption plus a labeled internal ledger
fault distinguishes reserving3 from trusting old1, without a fabricated late
end-to-end equality cap. Other internal controls cover warning shapes, final
subset and checked inventory overflow.
Scoped 128 KiB workers perform actual verification/assertions/drop for deep128
and wide129 expression nodes (success and target-only mismatch), plus the genuine
151-byte late refusal. All original prerequisites are prepared outside those
workers. Separate E0 target-only deep/wide and signed-zero geometry/scalar/history
cases exercise complete equality. Positive-zero bits in actual returned model
help distinguish moving the parsed model from substituting desired intent.
Before execution, source review prompted correcting the carried module summary;
parent readback caught an unnamed-source test slice, replaced with the exact
independent 71-byte original literal. Local type inspection corrected the f32
geometry bit assertion. These are pre-execution authoring corrections, not
runtime failures. No prior tests or stack sizes are edited. Only touched Rust
files were directly formatted with skip_children; compiler, full prior outcome
comparison, Clippy, format and measured prologue/stack gates belong to the parent.
No runtime result was claimed at that initial handoff; see the correction below.
The selected retained/decode/equality domain remains logical, not universal heap
or before-every-allocation accounting. Existing traversal/path-copy, delimiter,
ordinary-open, Pratt and duplicate-name scratch keep their independent controls
and stated exclusions; model/validator/numeric/decoder internals and native stack
remain separate domains. File counts do not tightly bound traversal path bytes.
Actual lifetime coercion and stack headroom still require the parent gates.
## Initial focused failure and test-oracle correction
Parent's initial focused run of `4872141c036c16825f1c098bd7cf44709897e814`
compiled successfully and completed 9 passes / 2 failures. The failing assertions
incorrectly required empty warnings in the rich OA/SA and four-file fixtures.
The original run, log and artifact remain preserved under the parent's
`analysis-output-acceptance-gates-20260925/acceptance-focused-4872141-1` records;
artifact cba18de5 prefix, 43,060,224 bytes. Those failures are not erased or called
production failures. The other nine controls, including both late byte caps and
scoped 128 KiB verification/drop, passed in that run.
Source-derived correction: validation.rs emits unallocated_leaf first, with
field allocation and message "Allocate leaf 'Work' to a performer."; its later
coverage.validate call emits work_without_behavior_context, field None, message
"Include this leaf activity/function in a process/chain or scenario through a
work action or exchange occurrence." Both are Warning, object ID2, empty related,
and map to the full Work declaration. The first local derivation missed the
coverage advisory; independent review caught it before the correction commit.
The pre-existing project expected_leaf_work_warnings oracle confirms both/order.
New tests now require both complete mapped diagnostics, with independent literal
spans: rich OA/SA main338..403 line6 column1, or Unicode/CRLF351..416 line7 column1;
four-file b.arcadia25..90 line2 column1. Warning storage is (2,0,227) for main,
(2,0,221) for b.arcadia. Final rich entries5/text1169,1212,1243,1286 for OA LF,
OA transformed, SA LF, SA transformed; four-file entries6/text747. Production
acceptance and all original test bodies remain unchanged. This is a test-only
oracle repair plus this note; corrected runtime acceptance awaits parent gates.
@@ -0,0 +1,102 @@
# Ordinary source8 compatibility implementation
Base `edf096cd33350865ecfc3c65ec266a6c0d3c4000`, selected parent contract
`harness/ordinary-source8-compatibility-contract-20260925.md` at root fb9b0c5c.
The public writer already emits8. The ordinary Parser::unit version list now
accepts8 and its unsupported-version diagnostic names the accurate range.
Current ordinary resolution already checks model limits and returns no migration;
3–7 retain their existing migration paths. No new converter or dispatch adapter
was introduced. Comments distinguish this ordinary repair from private mixed
analysis authoring.
Production change is intentionally confined to that version boundary. Long
analysis constructors retain their explicit conversion refusal, and short typed
declarations remain outside the ordinary grammar. Mixed cfg gates, formatter,
public SourceIndex/update semantics, ID stabilization, resource/depth policies
and codec switches are unchanged. Ordinary8 is not complete analysis/source8.
Six new tests use the ordinary_source8 module filter. They reuse the substantial
six-file source-project example and historical reviewed-prose fixture for complete
model/current formatter and bundle/index idempotence. Other groups cover3–7
migration to stable8; nested included declarations with omitted IDs, UTF-8/CRLF,
comments and actual returned index/identity spans; stale compiled input; short
and long analysis rejection, future/malformed headers and caller limits. Public
checked-save exercises both source and JSON, actual returned bytes/reopen/repeat
save, and rejection of the real formatter's omitted definition catalog while
preserving existing destination bytes. These are authored evaluators, not passing
execution claims. The three-file CLI preview literals additionally compile to an independently constructed complete Project before and after only the team alias changes; actual CLI execution remains a separate parent gate.
The prior test exception below repairs fixture construction without weakening
its semantics; all other prior test bodies/expectations remain unchanged. The16 formerly header-masked
library workflows must run their intended nested/update/migration/transaction
assertions; new downstream failures must be investigated on their own merits.
Parent owns focused/full/integration/CLI/non-test consumer/Clippy/format gates,
including previously unreached ordinary JSON branches. DEPTH_128_VALIDATED stays
false, and ordinary8 does not qualify the private depth candidate, user-deferred
Linux or the separately recorded full-pipeline setup limitation. Source/public
save equality and no-staging-before-validation safeguards remain mandatory.
Only changed Rust files were directly formatted with skip_children; source review
found no production blocker. Compiler/runtime acceptance remains parent-owned.
## Authorized pre-execution evaluator repair
Independent integration inventory found source7_contract's
source7_source_bundle_migration_is_token_only_and_new_link_stays_with_caller
attempted to replace formatted header7 with commented6, although the formatter
already emits8. That replacement was a no-op; the later expected7 was stale too.
Parent authorized this one existing evaluator correction: first assert actual
header8, replace8 with the original commented6 fixture, assert the comment and
single6 token, and expect only6->8 during migration. All exact file/comment/module,
whole-model, caller ownership and recompilation assertions remain, as does the
original test name. This is a static finding before execution, not a reported
runtime failure; no other legacy evaluator or formatter is changed.
## Focused fixture repair after initial execution
Parent's focused run at 33b86a4d completed five tests and failed one: the example
bundle has no trace records, contrary to the new fixture's nonempty assertion.
The repair explicitly authors a Participant trace from fixed OA rescue-team UUID
102 to fixed SA system UUID301, checks endpoint kinds and the complete trace,
and includes it in the unchanged whole-model source/bundle roundtrip assertions.
Historical reviewed-prose trace/baseline coverage remains. Independent source
review also identified the actual short case spellings oa_analysis/sa_analysis;
both now appear in the ordinary refusal table alongside the invalid case aliases.
Production and the other evaluators are unchanged. These corrections are not a
runtime pass claim; the initial five-pass/one-failure record remains preserved.
## Obsolete private header assertions after full execution
Parent's full run at 520e50ce completed 298 tests: 294 passed and four failed.
All 16 previously header-masked ordinary workflows passed. The four failures
were private analysis tests that still expected an empty ordinary schema8
project to be rejected. The scoped repair accepts ordinary8 with no migration,
compares complete ordinary OA/SA projects where the existing fixed-ID fixtures
provide that oracle, and explicitly embeds actual typed source or canonical
text in schema8 to preserve public typed refusal. Declaration refusal coverage
now includes versions3 through8. Existing numeric/lowering/model assertions stay.
Only these evaluator names change:
- inherited_version_root_and_short_word_contracts_do_not_enable_public8 ->
inherited_version_root_and_short_word_contracts_do_not_enable_public_typed8
- public_source_headers_and_existing_numeric_token_controls_are_unchanged ->
public_ordinary_source8_preserves_typed_refusal_and_numeric_controls
Production is unchanged. The initial 294-pass/four-failure evidence remains;
repaired outcomes require the parent's next runtime gate.
## Historical schema4 JSON oracle reconciliation
Parent's r3_migration integration run at 0c07f53a completed four passes and one
failure. Its whole-JSON expected value updated only schema_version, omitting the
current definitions catalog. Project4::into_current already creates an empty
catalog; Project serialization requires that schema8 field. The test now asserts
that the original fixture has no definitions field and the decoded catalog is
empty, then adds exactly definitions:{} to expected JSON. Complete JSON equality,
historical baseline/prose checks, migration and reopen checks remain unchanged.
No production behavior or frozen historical fixture changes.
The parent reports completed core, six other selected integration gates and both
actual CLI operations at 0c07f53a; that evidence remains reusable because this
repair changes only the affected integration evaluator and this note. The initial
r3 four-pass/one-failure record remains preserved. Parent owns the affected r3
inventory/rerun and all-target Clippy/format checks on the repaired pin; this note
does not claim their outcome or repeat unrelated runtime acceptance.