Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b06b8ea1b | ||
|
|
0c07f53ab5 | ||
|
|
520e50ce62 | ||
|
|
33b86a4d6b | ||
|
|
edf096cd33 | ||
|
|
4872141c03 |
@@ -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)
|
||||
@@ -1548,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");
|
||||
@@ -2283,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.
|
||||
//!
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -111,9 +133,8 @@ fn parse_text_producers(
|
||||
)
|
||||
}
|
||||
|
||||
/// Explicit retained text and entry producer fixture route.
|
||||
#[cfg(test)]
|
||||
fn parse_entry_producers(
|
||||
/// 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();
|
||||
|
||||
+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,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