Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b06b8ea1b | ||
|
|
0c07f53ab5 | ||
|
|
520e50ce62 | ||
|
|
33b86a4d6b | ||
|
|
edf096cd33 | ||
|
|
4872141c03 | ||
|
|
29417130c7 | ||
|
|
a629ae01d4 |
@@ -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(¤t).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);
|
||||
}
|
||||
+2
-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;
|
||||
|
||||
+175
@@ -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;
|
||||
+845
@@ -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 = ⌖
|
||||
} // 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);
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user