Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bab6f96a5b | ||
|
|
8555e417ee | ||
|
|
ddf823a3d3 | ||
|
|
042e25efc2 | ||
|
|
7b06b8ea1b | ||
|
|
0c07f53ab5 | ||
|
|
520e50ce62 | ||
|
|
33b86a4d6b |
@@ -3,10 +3,9 @@ mod raw_tokens;
|
||||
#[path = "language/semantic_source.rs"]
|
||||
mod semantic_source;
|
||||
mod source3_bridge;
|
||||
// Staged syntax foundation only. Public source8 admission waits for complete
|
||||
// declaration lowering, source updates and the root-owned resource gate.
|
||||
#[cfg(test)]
|
||||
// Mixed source8 is exposed only through the separate borrowed read-only checker.
|
||||
mod analysis_source;
|
||||
pub use analysis_source::{CheckedMixed, check_mixed_bundle};
|
||||
#[path = "migration_v2/source.rs"]
|
||||
mod v2_source;
|
||||
use crate::*;
|
||||
@@ -338,7 +337,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)
|
||||
@@ -480,7 +479,6 @@ fn parse_document(source: &str, limits: ParseLimits) -> Result<ParsedDocument, V
|
||||
cursor: 0,
|
||||
value_depth: 0,
|
||||
limits,
|
||||
#[cfg(test)]
|
||||
mixed: None,
|
||||
}
|
||||
.document()
|
||||
@@ -916,7 +914,6 @@ pub(crate) fn parse_unit(
|
||||
cursor: 0,
|
||||
value_depth: 0,
|
||||
limits,
|
||||
#[cfg(test)]
|
||||
mixed: None,
|
||||
}
|
||||
.unit(entry, true)
|
||||
@@ -1215,12 +1212,10 @@ struct Parser<'a> {
|
||||
cursor: usize,
|
||||
value_depth: usize,
|
||||
limits: ParseLimits,
|
||||
#[cfg(test)]
|
||||
mixed: Option<analysis_source::mixed::State<'a>>,
|
||||
}
|
||||
impl Parser<'_> {
|
||||
fn token(&self, index: usize) -> Option<Lexeme> {
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &self.mixed {
|
||||
return mixed.token(index);
|
||||
}
|
||||
@@ -1236,7 +1231,6 @@ impl Parser<'_> {
|
||||
})
|
||||
}
|
||||
fn syntax_entry(&mut self) -> Result<(), Error> {
|
||||
#[cfg(test)]
|
||||
{
|
||||
let span = self.current().span;
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
@@ -1266,9 +1260,7 @@ impl Parser<'_> {
|
||||
if self.current().class != TokenClass::String {
|
||||
return Err(self.error("Expected a double-quoted string."));
|
||||
}
|
||||
#[cfg(test)]
|
||||
let span = self.current().span;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
let raw = &self.source[span.start..span.end];
|
||||
let value = mixed.quoted_text(raw, span)?;
|
||||
@@ -1290,9 +1282,8 @@ impl Parser<'_> {
|
||||
}
|
||||
fn optional_id(&mut self) -> Result<Id, Error> {
|
||||
if self.text() != "id" {
|
||||
#[cfg(test)]
|
||||
if self.mixed.is_some() {
|
||||
return Err(self.error("Private mixed syntax requires explicit IDs."));
|
||||
return Err(self.error("Mixed source8 requires explicit UUIDs."));
|
||||
}
|
||||
return Ok(Id::new_v4());
|
||||
}
|
||||
@@ -1321,21 +1312,15 @@ impl Parser<'_> {
|
||||
Ok(id)
|
||||
}
|
||||
fn own_text(&mut self, bytes: usize, span: Span) -> Result<(), Error> {
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.own_text(bytes, span)?;
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
let _ = (bytes, span);
|
||||
Ok(())
|
||||
}
|
||||
fn release_text(&mut self, bytes: usize) {
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.release_text(bytes);
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
let _ = bytes;
|
||||
}
|
||||
fn fields(&mut self) -> Result<BTreeMap<String, Ast>, Error> {
|
||||
self.take("{")?;
|
||||
@@ -1383,7 +1368,6 @@ impl Parser<'_> {
|
||||
.span
|
||||
.end;
|
||||
self.take(";")?;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.projection(1, Span { start, end })?;
|
||||
}
|
||||
@@ -1412,7 +1396,6 @@ impl Parser<'_> {
|
||||
self.value_depth += 1;
|
||||
let result = self.value_inner();
|
||||
self.value_depth -= 1;
|
||||
#[cfg(test)]
|
||||
if let Ok(ast) = &result {
|
||||
let end = self
|
||||
.token(self.cursor - 1)
|
||||
@@ -1433,7 +1416,6 @@ impl Parser<'_> {
|
||||
fn value_inner(&mut self) -> Result<Ast, Error> {
|
||||
let token = self.current();
|
||||
let offset = token.span.start;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &self.mixed
|
||||
&& (token.class == TokenClass::Number || matches!(self.text(), "-" | "+"))
|
||||
{
|
||||
@@ -1541,19 +1523,16 @@ impl Parser<'_> {
|
||||
fn unit(&mut self, entry: bool, includes_allowed: bool) -> Result<SourceUnit, Error> {
|
||||
let mut header = if entry {
|
||||
self.take("arcadia")?;
|
||||
#[cfg(test)]
|
||||
let mixed_mode = self.mixed.is_some();
|
||||
#[cfg(not(test))]
|
||||
let mixed_mode = false;
|
||||
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."
|
||||
"Mixed source 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");
|
||||
@@ -1561,7 +1540,6 @@ impl Parser<'_> {
|
||||
self.take(";")?;
|
||||
let start = self.current().span.start;
|
||||
self.take("project")?;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.record(false, Span { start, end: start })?;
|
||||
}
|
||||
@@ -1606,7 +1584,6 @@ impl Parser<'_> {
|
||||
let mut open: Vec<usize> = Vec::new();
|
||||
loop {
|
||||
let offset = self.current().span.start;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed
|
||||
&& mixed.typed_header(self.cursor, open.is_empty())
|
||||
{
|
||||
@@ -1658,7 +1635,6 @@ impl Parser<'_> {
|
||||
if !includes_allowed {
|
||||
return Err(self.error("An include requires a source bundle; single-string parsing never reads files."));
|
||||
}
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.include(Span {
|
||||
start: offset,
|
||||
@@ -1705,7 +1681,6 @@ impl Parser<'_> {
|
||||
header = Some(entry_header);
|
||||
continue;
|
||||
}
|
||||
#[cfg(test)]
|
||||
if self.mixed.is_some() && analysis_source::mixed::long_analysis_word(self.text()) {
|
||||
return Err(self.error("Use the short source8 analysis declaration word."));
|
||||
}
|
||||
@@ -1713,7 +1688,6 @@ impl Parser<'_> {
|
||||
let word = self.current().text(self.source);
|
||||
let kind = declaration_kind(word)
|
||||
.ok_or_else(|| self.error(format!("Unknown declaration '{word}'.")))?;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.record(
|
||||
matches!(kind, DeclKind::Element(..) | DeclKind::Relation(..)),
|
||||
@@ -1750,7 +1724,6 @@ impl Parser<'_> {
|
||||
let body_start = self.current().span.end;
|
||||
self.take("{")?;
|
||||
let lexical_owner = open.last().map(|&owner| declarations[owner].id);
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
let span = Span {
|
||||
start: offset,
|
||||
@@ -2283,6 +2256,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.
|
||||
//!
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
//! Unresolved typed source8 syntax, staged for the complete source adapter.
|
||||
//! This module does not admit a Project or enable public source8.
|
||||
//! Private typed syntax and complete read-only mixed source8 resolution.
|
||||
mod budget;
|
||||
mod cursor;
|
||||
mod declarations;
|
||||
#[cfg(test)]
|
||||
mod index;
|
||||
mod lex;
|
||||
mod lower;
|
||||
pub(super) mod mixed;
|
||||
mod syntax;
|
||||
#[cfg(test)]
|
||||
mod update;
|
||||
#[cfg(test)]
|
||||
mod write;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -18,6 +20,7 @@ use super::Span;
|
||||
use crate::analysis::{BinaryOp, UnaryOp};
|
||||
use crate::analysis_numeric::Exact;
|
||||
use budget::Budget;
|
||||
pub use mixed::project::{CheckedMixed, check_mixed_bundle};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Error {
|
||||
@@ -104,6 +107,7 @@ pub enum Binding {
|
||||
Expression(Expression),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn parse(source: &str, budget: &mut Budget) -> Result<Binding, Error> {
|
||||
let tokens = lex::tokens(source, budget)?;
|
||||
let mut cursor = cursor::Cursor::new(source, &tokens, budget);
|
||||
|
||||
@@ -221,6 +221,7 @@ impl Budget {
|
||||
Ok(value)
|
||||
}
|
||||
/// Immutable exact policy snapshot for private deliberate-snapshot continuation.
|
||||
#[cfg(test)]
|
||||
pub(super) fn limits(&self) -> Limits {
|
||||
self.limits
|
||||
}
|
||||
@@ -294,6 +295,7 @@ impl Budget {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub fn numeric_units(&self) -> u64 {
|
||||
self.numeric.usage().numeric_units
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ impl Cursor<'_, '_> {
|
||||
self.cursor += 1;
|
||||
Ok(span)
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(super) fn end(&self) -> Result<(), Error> {
|
||||
if self.cursor != self.tokens.len() {
|
||||
Err(error(
|
||||
|
||||
@@ -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} }}"
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
//! single complete-Project semantic admission after merging these additions.
|
||||
use super::declarations::{self as d, Declaration, DeclarationKind};
|
||||
use super::*;
|
||||
#[cfg(test)]
|
||||
use crate::Project;
|
||||
use crate::analysis as a;
|
||||
use crate::analysis_numeric::Dimension;
|
||||
use crate::{Element, ElementBody, Id, Kind, Layer, Project, SharedBody};
|
||||
use crate::{Element, ElementBody, Id, Kind, Layer, SharedBody};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
/// Caller-assigned file key; spans are original offsets within that file.
|
||||
#[cfg(test)]
|
||||
pub(super) struct Unit {
|
||||
pub file: usize,
|
||||
pub declarations: Vec<Declaration>,
|
||||
@@ -180,6 +183,7 @@ impl Tables {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn ordinary(&mut self, project: &Project, budget: &Budget) -> Result<(), Error> {
|
||||
let at = Span::default();
|
||||
self.insert(project.id, &project.alias, Category::Project, at, budget)?;
|
||||
@@ -270,6 +274,7 @@ impl Tables {
|
||||
/// Inputs must come from the same live Budget and stabilization pass. No scan,
|
||||
/// exact conversion or reference charge occurs here. The ordinary skeleton is
|
||||
/// not semantically admitted: its typed forward references may need additions.
|
||||
#[cfg(test)]
|
||||
pub(super) fn lower(
|
||||
ordinary: &Project,
|
||||
units: &[Unit],
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
//! value-end entries, projected value/field/header/declaration positions, lexical
|
||||
//! owner slots and the file table. Checks do not recharge syntax or latch errors;
|
||||
//! every failure must return immediately. Tokens themselves use the token bound.
|
||||
//! This deliberately bounded staging policy is not calibrated public source8.
|
||||
//! The public read checker selects both producers; editing remains separately staged.
|
||||
|
||||
use super::{
|
||||
budget::{Budget, Limits},
|
||||
@@ -53,11 +53,15 @@ pub(super) struct ParsedMixedSyntax<'s> {
|
||||
struct FileSyntax<'s> {
|
||||
path: &'s str,
|
||||
source: &'s str,
|
||||
// Retained original indexing metadata; the read result preserves its accounted ownership.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
tokens: Vec<lex::Token>,
|
||||
ordinary: SourceUnit,
|
||||
typed: Vec<declarations::Declaration>,
|
||||
positions: Vec<(Id, EditDeclaration)>,
|
||||
// A lexical owner is an occurrence index, not only a possibly repeated UUID.
|
||||
// Retained original indexing metadata; the read result preserves its accounted ownership.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
lexical_owners: Vec<Option<usize>>,
|
||||
}
|
||||
|
||||
@@ -256,7 +260,7 @@ impl State<'_> {
|
||||
return Err(Error {
|
||||
code: "invalid_source",
|
||||
offset: declaration.span.start,
|
||||
message: "Private mixed syntax requires explicit IDs.".into(),
|
||||
message: "Mixed source8 requires explicit UUIDs.".into(),
|
||||
});
|
||||
}
|
||||
self.typed.push(declaration);
|
||||
@@ -276,17 +280,22 @@ pub(in crate::language) fn long_analysis_word(word: &str) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn parse(
|
||||
bundle: &SourceBundle,
|
||||
limits: ProjectLimits,
|
||||
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
|
||||
let profile = Limits::source8(ParseLimits {
|
||||
let profile = source_profile(limits);
|
||||
parse_with_profile(bundle, limits, profile)
|
||||
}
|
||||
|
||||
fn source_profile(limits: ProjectLimits) -> Limits {
|
||||
Limits::source8(ParseLimits {
|
||||
// Storage is not the caller's final model record/ordered-step accounting.
|
||||
max_source_bytes: limits.model.max_source_bytes,
|
||||
max_step_references: limits.model.max_step_references,
|
||||
..ParseLimits::UNLIMITED
|
||||
});
|
||||
parse_with_profile(bundle, limits, profile)
|
||||
})
|
||||
}
|
||||
|
||||
fn located(path: &str, source: &str, error: Error) -> Vec<SourceDiagnostic> {
|
||||
@@ -297,6 +306,7 @@ fn located(path: &str, source: &str, error: Error) -> Vec<SourceDiagnostic> {
|
||||
}]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn parse_with_profile(
|
||||
bundle: &SourceBundle,
|
||||
limits: ProjectLimits,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! Checks existing logical phase totals; does not meter every parser allocation.
|
||||
use super::*;
|
||||
use project::accounting::{Ledger, Slots};
|
||||
#[cfg(test)]
|
||||
use project::ordinary_index::rename::emission::PendingRename;
|
||||
|
||||
struct SnapshotPolicy {
|
||||
@@ -10,17 +11,20 @@ struct SnapshotPolicy {
|
||||
}
|
||||
|
||||
/// The allocation-owning bundle guard. No production constructor exists yet.
|
||||
#[cfg(test)]
|
||||
struct RetainedSnapshot {
|
||||
bundle: SourceBundle,
|
||||
policy: SnapshotPolicy,
|
||||
retained: Slots,
|
||||
}
|
||||
enum SnapshotBacking<'s> {
|
||||
#[cfg(test)]
|
||||
Pending(&'s PendingRename<'s>),
|
||||
Borrowed {
|
||||
bundle: &'s SourceBundle,
|
||||
policy: SnapshotPolicy,
|
||||
},
|
||||
#[cfg(test)]
|
||||
Retained(&'s RetainedSnapshot),
|
||||
}
|
||||
|
||||
@@ -29,8 +33,12 @@ pub(super) struct CarriedInput<'s> {
|
||||
backing: SnapshotBacking<'s>,
|
||||
}
|
||||
enum GuardBacking<'s> {
|
||||
#[cfg(test)]
|
||||
Pending(&'s PendingRename<'s>),
|
||||
Borrowed,
|
||||
Borrowed {
|
||||
_bundle: &'s SourceBundle,
|
||||
},
|
||||
#[cfg(test)]
|
||||
Retained(&'s RetainedSnapshot),
|
||||
}
|
||||
|
||||
@@ -40,6 +48,18 @@ pub(super) struct CarriedGuard<'s> {
|
||||
backing: GuardBacking<'s>,
|
||||
}
|
||||
impl<'s> CarriedInput<'s> {
|
||||
fn fresh(bundle: &'s SourceBundle, limits: ProjectLimits) -> Self {
|
||||
Self {
|
||||
backing: SnapshotBacking::Borrowed {
|
||||
bundle,
|
||||
policy: SnapshotPolicy {
|
||||
limits: source_profile(limits),
|
||||
project_limits: limits,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(super) fn from_pending<'t: 's>(pending: &'s PendingRename<'t>) -> Self {
|
||||
Self {
|
||||
backing: SnapshotBacking::Pending(pending),
|
||||
@@ -47,6 +67,7 @@ impl<'s> CarriedInput<'s> {
|
||||
}
|
||||
fn into_parts(self) -> (&'s SourceBundle, SnapshotPolicy, CarriedGuard<'s>) {
|
||||
match self.backing {
|
||||
#[cfg(test)]
|
||||
SnapshotBacking::Pending(pending) => {
|
||||
let (limits, project_limits) = pending.carried_policy();
|
||||
(
|
||||
@@ -64,9 +85,10 @@ impl<'s> CarriedInput<'s> {
|
||||
bundle,
|
||||
policy,
|
||||
CarriedGuard {
|
||||
backing: GuardBacking::Borrowed,
|
||||
backing: GuardBacking::Borrowed { _bundle: bundle },
|
||||
},
|
||||
),
|
||||
#[cfg(test)]
|
||||
SnapshotBacking::Retained(owner) => (
|
||||
&owner.bundle,
|
||||
SnapshotPolicy {
|
||||
@@ -83,8 +105,10 @@ impl<'s> CarriedInput<'s> {
|
||||
impl CarriedGuard<'_> {
|
||||
pub(super) fn slots(&self) -> Slots {
|
||||
match &self.backing {
|
||||
#[cfg(test)]
|
||||
GuardBacking::Pending(pending) => pending.carried_slots(),
|
||||
GuardBacking::Borrowed => Slots::default(),
|
||||
GuardBacking::Borrowed { .. } => Slots::default(),
|
||||
#[cfg(test)]
|
||||
GuardBacking::Retained(owner) => owner.retained,
|
||||
}
|
||||
}
|
||||
@@ -105,6 +129,7 @@ impl CarriedGuard<'_> {
|
||||
}
|
||||
|
||||
/// Input already binds the actual bundle; callers cannot supply a replacement.
|
||||
#[cfg(test)]
|
||||
fn parse_carried(input: CarriedInput<'_>) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
|
||||
let (bundle, policy, guard) = input.into_parts();
|
||||
parse_impl(
|
||||
@@ -205,3 +230,12 @@ mod text_producer;
|
||||
|
||||
#[cfg(test)]
|
||||
mod entry_producer;
|
||||
|
||||
pub(super) fn parse_fresh(
|
||||
bundle: &SourceBundle,
|
||||
limits: ProjectLimits,
|
||||
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
|
||||
parse_entry_producers(CarriedInput::fresh(bundle, limits))
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod public_read;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//! Real public-constructor wiring; arbitrary fixture profiles are not public policy.
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn public_read_fresh_constructor_keeps_zero_carry_both_modes_and_exact_policy() {
|
||||
let bundle = SourceBundle {
|
||||
entry: "main.arcadia".into(),
|
||||
files: [("main.arcadia".into(), "arcadia 8;\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {\n\n}\n".into())].into(),
|
||||
};
|
||||
let original = bundle.clone();
|
||||
for (source_bytes, references, expected_bytes, expected_tokens, expected_references) in [
|
||||
(4096, 17, 4096, 4096, 17),
|
||||
(usize::MAX, usize::MAX, 64 * 1024 * 1024, 1_048_576, 500_000),
|
||||
] {
|
||||
let limits = ProjectLimits {
|
||||
max_files: 7,
|
||||
max_include_depth: 9,
|
||||
max_include_edges: 11,
|
||||
model: ParseLimits {
|
||||
max_source_bytes: source_bytes,
|
||||
max_graph_objects: 3,
|
||||
max_records: 1,
|
||||
max_step_references: references,
|
||||
},
|
||||
};
|
||||
// This is the same production helper the exported checker calls.
|
||||
let syntax = parse_fresh(&bundle, limits).unwrap();
|
||||
let guard = syntax.carried.as_ref().expect("explicit carried mode");
|
||||
assert!(matches!(guard.backing, GuardBacking::Borrowed { .. }));
|
||||
assert_eq!(guard.slots(), Slots::default());
|
||||
assert_eq!(syntax.budget.producer_text(), Some((8, 0)));
|
||||
assert_eq!(syntax.budget.producer_entries(), Some(11));
|
||||
assert_eq!(syntax.metadata, 11);
|
||||
let profile = syntax.budget.limits();
|
||||
assert_eq!(profile.bytes, expected_bytes);
|
||||
assert_eq!(profile.tokens, expected_tokens);
|
||||
assert_eq!(profile.entries, 262_144);
|
||||
assert_eq!(profile.references, expected_references);
|
||||
assert_eq!(profile.frames, 256);
|
||||
assert_eq!(profile.numeric_units, 50_000_000);
|
||||
assert_eq!(syntax.limits.max_files, 7);
|
||||
assert_eq!(syntax.limits.max_include_depth, 9);
|
||||
assert_eq!(syntax.limits.max_include_edges, 11);
|
||||
assert_eq!(syntax.limits.model.max_source_bytes, source_bytes);
|
||||
assert_eq!(syntax.limits.model.max_graph_objects, 3);
|
||||
assert_eq!(syntax.limits.model.max_records, 1);
|
||||
assert_eq!(syntax.limits.model.max_step_references, references);
|
||||
let prior = parse(&bundle, limits).unwrap();
|
||||
assert_eq!(syntax.budget.usage, prior.budget.usage);
|
||||
assert_eq!(syntax.budget.numeric_units(), prior.budget.numeric_units());
|
||||
let resolved = project::resolve(syntax).unwrap();
|
||||
let prior = project::resolve(prior).unwrap();
|
||||
let checked = project::check_mixed_bundle(&bundle, limits).unwrap();
|
||||
let mut expected = crate::Project::empty();
|
||||
expected.id = Id::from_u128(1);
|
||||
expected.alias = "p".into();
|
||||
expected.name = "Project".into();
|
||||
assert_eq!(resolved.project(), &expected);
|
||||
assert_eq!(prior.project(), &expected);
|
||||
assert_eq!(checked.project(), &expected);
|
||||
assert!(checked.warnings().is_empty());
|
||||
assert_eq!(resolved.warnings(), prior.warnings());
|
||||
}
|
||||
assert_eq!(bundle, original);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Private complete-Project admission from the actual complete syntax witness.
|
||||
//! Consumed read-only typed index; no update authority, public source8 dispatch or replacement policy.
|
||||
//! Complete-Project admission and opaque public read-only mixed source8 witness.
|
||||
//! The separate typed index/edit chain remains test-only; no replacement policy.
|
||||
use super::super::{
|
||||
Binding, Budget, Error as TypedError, Expression, ExpressionKind, Reference, Span,
|
||||
declarations as d, error, lower,
|
||||
@@ -22,17 +22,108 @@ pub(super) fn text_producer_inventory(syntax: &ParsedMixedSyntax<'_>) -> Result<
|
||||
accounting::syntax(syntax, &mut ledger)?;
|
||||
Ok(ledger.live)
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(in crate::language::analysis_source) mod ordinary_index;
|
||||
|
||||
/// An admitted mixed source8 model borrowing its complete original source bundle.
|
||||
///
|
||||
/// This non-Clone result grants immutable validation queries only. It is not an
|
||||
/// editable source index, stabilized output, or permission to format/save a typed
|
||||
/// model through the ordinary source APIs. All input UUIDs must be explicit.
|
||||
///
|
||||
/// The source cannot be mutated while a later query uses the result:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use oa_core::{language::check_mixed_bundle, source_project::{SourceBundle, ProjectLimits}};
|
||||
/// let mut bundle = SourceBundle {
|
||||
/// entry: "main.arcadia".into(),
|
||||
/// files: [("main.arcadia".into(),
|
||||
/// "arcadia 8; project p \"P\" id \"00000000-0000-0000-0000-000000000001\" {}".into())].into(),
|
||||
/// };
|
||||
/// let checked = check_mixed_bundle(&bundle, ProjectLimits::default()).unwrap();
|
||||
/// bundle.files.clear();
|
||||
/// assert_eq!(checked.project().alias, "p");
|
||||
/// ```
|
||||
///
|
||||
/// Nor can the result outlive that source:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use oa_core::{language::check_mixed_bundle, source_project::{SourceBundle, ProjectLimits}};
|
||||
/// let checked = {
|
||||
/// let bundle = SourceBundle {
|
||||
/// entry: "main.arcadia".into(),
|
||||
/// files: [("main.arcadia".into(),
|
||||
/// "arcadia 8; project p \"P\" id \"00000000-0000-0000-0000-000000000001\" {}".into())].into(),
|
||||
/// };
|
||||
/// check_mixed_bundle(&bundle, ProjectLimits::default()).unwrap()
|
||||
/// };
|
||||
/// assert_eq!(checked.project().alias, "p");
|
||||
/// ```
|
||||
pub struct CheckedMixed<'s> {
|
||||
resolved: ResolvedMixedProject<'s>,
|
||||
}
|
||||
impl CheckedMixed<'_> {
|
||||
/// The complete admitted model, without mutation or ownership extraction.
|
||||
pub fn project(&self) -> &Project {
|
||||
self.resolved.project()
|
||||
}
|
||||
/// Actual located non-error validation diagnostics in validation order.
|
||||
pub fn warnings(&self) -> &[SourceDiagnostic] {
|
||||
self.resolved.warnings()
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate a complete mixed ordinary/typed source8 bundle without changing bytes.
|
||||
///
|
||||
/// Every project, ordinary/typed declaration and typed parameter requires an
|
||||
/// explicit UUID. Supplied files must be canonical and reachable from the entry.
|
||||
/// Errors return no partial admitted model. There is no migration, ID generation,
|
||||
/// filesystem discovery, formatting or editable-index construction.
|
||||
///
|
||||
/// Caller policy is applied across the entire bundle with the existing bounded
|
||||
/// source8 syntax/storage/numeric policy; this is not an all-heap memory bound.
|
||||
/// Ordinary parse/compile/edit/save APIs and CLI discovery remain separate.
|
||||
///
|
||||
/// ```
|
||||
/// use oa_core::{language::check_mixed_bundle, source_project::{SourceBundle, ProjectLimits}};
|
||||
/// let bundle = SourceBundle {
|
||||
/// entry: "main.arcadia".into(),
|
||||
/// files: [("main.arcadia".into(),
|
||||
/// "arcadia 8; project p \"P\" id \"00000000-0000-0000-0000-000000000001\" {}".into())].into(),
|
||||
/// };
|
||||
/// let checked = check_mixed_bundle(&bundle, ProjectLimits::default()).unwrap();
|
||||
/// assert_eq!(checked.project().alias, "p");
|
||||
/// assert!(checked.warnings().is_empty());
|
||||
/// ```
|
||||
pub fn check_mixed_bundle(
|
||||
bundle: &crate::source_project::SourceBundle,
|
||||
limits: crate::source_project::ProjectLimits,
|
||||
) -> Result<CheckedMixed<'_>, Vec<SourceDiagnostic>> {
|
||||
let syntax = super::carried::parse_fresh(bundle, limits)?;
|
||||
let resolved = resolve(syntax).map_err(|failure| failure.diagnostics)?;
|
||||
Ok(CheckedMixed { resolved })
|
||||
}
|
||||
/// Read-only authority: every component originates in the consumed syntax.
|
||||
pub(in crate::language::analysis_source) struct ResolvedMixedProject<'s> {
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
syntax: ParsedMixedSyntax<'s>,
|
||||
project: Project,
|
||||
warnings: Vec<SourceDiagnostic>,
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
static_limits: analysis::AdmissionLimits,
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
static_usage: analysis::AdmissionUsage,
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
expressions: Vec<lower::ExpressionSite>,
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
occurrences: Vec<lower::Occurrence>,
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
storage: Ledger,
|
||||
}
|
||||
impl ResolvedMixedProject<'_> {
|
||||
@@ -51,10 +142,12 @@ impl ResolvedMixedProject<'_> {
|
||||
/// Complete original bundle authority with a typed-only occurrence view.
|
||||
/// Ordinary globals are catalogued, but their authored occurrences are not indexed.
|
||||
/// This owner cannot be converted into the legacy typed-only edit authority.
|
||||
#[cfg(test)]
|
||||
pub(in crate::language::analysis_source) struct IndexedMixedProject<'s> {
|
||||
resolved: ResolvedMixedProject<'s>,
|
||||
data: super::super::index::IndexData,
|
||||
}
|
||||
#[cfg(test)]
|
||||
impl<'s> ResolvedMixedProject<'s> {
|
||||
pub fn index(mut self) -> Result<IndexedMixedProject<'s>, Failure> {
|
||||
let input = IndexInput {
|
||||
@@ -72,6 +165,7 @@ impl<'s> ResolvedMixedProject<'s> {
|
||||
})
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
impl IndexedMixedProject<'_> {
|
||||
pub fn project(&self) -> &Project {
|
||||
self.resolved.project()
|
||||
@@ -108,12 +202,14 @@ impl IndexedMixedProject<'_> {
|
||||
|
||||
/// Constructor-only borrowed adapter. Its private file table prevents fabrication;
|
||||
/// no API returns it and no alternative source/token table can be installed.
|
||||
#[cfg(test)]
|
||||
pub(in crate::language::analysis_source) struct IndexInput<'a> {
|
||||
files: &'a [FileSyntax<'a>],
|
||||
pub(in crate::language::analysis_source) project: &'a Project,
|
||||
pub(in crate::language::analysis_source) expressions: &'a [lower::ExpressionSite],
|
||||
pub(in crate::language::analysis_source) occurrences: &'a [lower::Occurrence],
|
||||
}
|
||||
#[cfg(test)]
|
||||
impl IndexInput<'_> {
|
||||
pub(in crate::language::analysis_source) fn declarations(
|
||||
&self,
|
||||
@@ -140,6 +236,7 @@ pub(in crate::language::analysis_source) struct Failure {
|
||||
}
|
||||
impl Failure {
|
||||
/// Canonical attribution when justified; otherwise a safe explicit start fallback.
|
||||
#[cfg(test)]
|
||||
fn index(syntax: &ParsedMixedSyntax<'_>, mut failure: lower::Failure) -> Self {
|
||||
failure.file = failure.file.filter(|&f| f < syntax.files.len());
|
||||
if failure.file.is_none_or(|file| {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -194,7 +194,6 @@ pub(super) fn row_key(source: &str, origins: &BTreeMap<Id, Id>) -> Option<String
|
||||
cursor: 0,
|
||||
value_depth: 0,
|
||||
limits: ParseLimits::UNLIMITED,
|
||||
#[cfg(test)]
|
||||
mixed: None,
|
||||
};
|
||||
let ast = parser.value().ok()?;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,812 @@
|
||||
//! External consumer: the linked oa-core library is not built with cfg(test).
|
||||
//! Expected models use authored public values, never a parser or formatter oracle.
|
||||
use oa_core::analysis as a;
|
||||
use oa_core::analysis_numeric::Exact;
|
||||
use oa_core::language::{ParseLimits, Span};
|
||||
use oa_core::source_project::{ProjectLimits, SourceBundle, SourceDiagnostic};
|
||||
pub use oa_core::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
// This existing helper constructs independent public model values only. It does
|
||||
// not import private parser/index/writer code or enable cfg(test) in oa-core.
|
||||
#[path = "../src/language/analysis_source/model_oracle.rs"]
|
||||
mod independent_model;
|
||||
|
||||
fn id(n: u128) -> Id {
|
||||
Id::from_u128(n)
|
||||
}
|
||||
|
||||
fn empty_project() -> Project {
|
||||
let mut p = Project::empty();
|
||||
p.id = id(1);
|
||||
p.alias = "p".into();
|
||||
p.name = "Project".into();
|
||||
p
|
||||
}
|
||||
|
||||
fn document(body: &str) -> String {
|
||||
format!(
|
||||
"arcadia 8;\nproject p \"Project\" id \"{}\" {{\n{body}\n}}\n",
|
||||
id(1)
|
||||
)
|
||||
}
|
||||
|
||||
fn single(body: &str) -> SourceBundle {
|
||||
SourceBundle {
|
||||
entry: "main.arcadia".into(),
|
||||
files: BTreeMap::from([("main.arcadia".into(), document(body))]),
|
||||
}
|
||||
}
|
||||
|
||||
fn succeeds(
|
||||
bundle: &SourceBundle,
|
||||
limits: ProjectLimits,
|
||||
expected: &Project,
|
||||
warnings: &[SourceDiagnostic],
|
||||
) {
|
||||
let original = bundle.clone();
|
||||
let checked = language::check_mixed_bundle(bundle, limits)
|
||||
.unwrap_or_else(|errors| panic!("expected complete read: {errors:#?}"));
|
||||
assert_eq!(checked.project(), expected);
|
||||
assert_eq!(checked.warnings(), warnings);
|
||||
assert_eq!(bundle, &original, "read success preserves every input byte");
|
||||
drop(checked);
|
||||
assert_eq!(bundle, &original);
|
||||
}
|
||||
|
||||
fn fails(bundle: &SourceBundle, limits: ProjectLimits) -> Vec<SourceDiagnostic> {
|
||||
let original = bundle.clone();
|
||||
let errors = match language::check_mixed_bundle(bundle, limits) {
|
||||
Ok(_) => panic!("invalid input returned a successful read witness"),
|
||||
Err(errors) => errors,
|
||||
};
|
||||
assert!(!errors.is_empty());
|
||||
assert_eq!(bundle, &original, "read refusal preserves every input byte");
|
||||
errors
|
||||
}
|
||||
|
||||
fn authored_span(source: &str, literal: &str) -> Span {
|
||||
assert_eq!(
|
||||
source.matches(literal).count(),
|
||||
1,
|
||||
"ambiguous authored span"
|
||||
);
|
||||
let start = source.find(literal).unwrap();
|
||||
Span {
|
||||
start,
|
||||
end: start + literal.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn error_at(
|
||||
bundle: &SourceBundle,
|
||||
errors: &[SourceDiagnostic],
|
||||
path: &str,
|
||||
span: Span,
|
||||
code: &str,
|
||||
message: &str,
|
||||
) {
|
||||
assert_eq!(errors.len(), 1, "{errors:#?}");
|
||||
let error = &errors[0];
|
||||
assert_eq!(error.path, path);
|
||||
assert_eq!(error.diagnostic.severity, validation::Severity::Error);
|
||||
assert_eq!(error.diagnostic.code, code);
|
||||
assert_eq!(error.diagnostic.message, message);
|
||||
assert_eq!(error.diagnostic.span, span);
|
||||
let source = bundle.files.get(path).map_or("", String::as_str);
|
||||
let prefix = &source[..span.start];
|
||||
assert_eq!(
|
||||
error.diagnostic.line,
|
||||
prefix.bytes().filter(|b| *b == b'\n').count() + 1
|
||||
);
|
||||
assert_eq!(
|
||||
error.diagnostic.column,
|
||||
prefix.rsplit('\n').next().unwrap().chars().count() + 1
|
||||
);
|
||||
}
|
||||
|
||||
fn rich(layer: Layer, crlf: bool) -> (SourceBundle, Project, Vec<SourceDiagnostic>) {
|
||||
let (ordinary, typed, work) = match layer {
|
||||
Layer::Oa => (
|
||||
include_str!("../src/language/analysis_source/lower/fixtures/ordinary-oa.arcadia"),
|
||||
include_str!("../src/language/analysis_source/lower/fixtures/typed-oa.arcadia"),
|
||||
"oa_activity",
|
||||
),
|
||||
Layer::Sa => (
|
||||
include_str!("../src/language/analysis_source/lower/fixtures/ordinary-sa.arcadia"),
|
||||
include_str!("../src/language/analysis_source/lower/fixtures/typed-sa.arcadia"),
|
||||
"sa_function",
|
||||
),
|
||||
};
|
||||
let mut expected = independent_model::project(layer);
|
||||
let mut main = ordinary
|
||||
.replace("\r\n", "\n")
|
||||
.replacen("arcadia 7;", "arcadia 8;", 1);
|
||||
assert!(main.starts_with("arcadia 8;"));
|
||||
// Forward typed uses refer back into nested ordinary Work in this file.
|
||||
main = main.replacen("{\n", "{\n include \"typed.arcadia\";\n", 1);
|
||||
if layer == Layer::Sa {
|
||||
let system = Element {
|
||||
id: id(999),
|
||||
alias: "system".into(),
|
||||
name: "System".into(),
|
||||
description: String::new(),
|
||||
parent: None,
|
||||
body: ElementBody::Sa(SaBody::System),
|
||||
};
|
||||
let at = main.rfind('}').unwrap();
|
||||
main.insert_str(
|
||||
at,
|
||||
&format!(
|
||||
" sa_system system \"System\" id \"{}\" {{}}\n",
|
||||
system.id
|
||||
),
|
||||
);
|
||||
assert!(expected.elements.insert(system.id, system).is_none());
|
||||
}
|
||||
let mut typed = typed.replace("\r\n", "\n");
|
||||
// Textual argument order is independent of the model's parameter-ID map.
|
||||
assert!(typed.contains("@combine(a: @receive_time, b: @dispatch_time)"));
|
||||
typed = typed.replace(
|
||||
"@combine(a: @receive_time, b: @dispatch_time)",
|
||||
"@combine(b: @dispatch_time, a: @receive_time)",
|
||||
);
|
||||
if crlf {
|
||||
main = format!("// 港🌊\r\n{}", main.replace('\n', "\r\n"));
|
||||
typed = typed.replace('\n', "\r\n");
|
||||
}
|
||||
let bundle = SourceBundle {
|
||||
entry: "main.arcadia".into(),
|
||||
files: BTreeMap::from([
|
||||
("main.arcadia".into(), main),
|
||||
("typed.arcadia".into(), typed),
|
||||
]),
|
||||
};
|
||||
// Two nested leaves, so four warnings, unlike the earlier one-Work fixture.
|
||||
// Existing validation orders both allocation warnings before coverage warnings.
|
||||
let mut warnings = Vec::new();
|
||||
for allocation in [true, false] {
|
||||
for (alias, display) in [("receive", "Receive"), ("dispatch", "Dispatch")] {
|
||||
let element = expected
|
||||
.elements
|
||||
.values()
|
||||
.find(|e| e.alias == alias)
|
||||
.unwrap();
|
||||
let literal = format!("{work} {alias} \"{display}\" id \"{}\" {{}}", element.id);
|
||||
let source = &bundle.files["main.arcadia"];
|
||||
let span = authored_span(source, &literal);
|
||||
let prefix = &source[..span.start];
|
||||
warnings.push(SourceDiagnostic {
|
||||
path: "main.arcadia".into(), related: Vec::new(),
|
||||
diagnostic: language::Diagnostic {
|
||||
severity: validation::Severity::Warning,
|
||||
code: if allocation { "unallocated_leaf" } else { "work_without_behavior_context" }.into(),
|
||||
object_id: Some(element.id),
|
||||
field: allocation.then(|| "allocation".into()),
|
||||
message: if allocation { format!("Allocate leaf '{display}' to a performer.") } else {
|
||||
"Include this leaf activity/function in a process/chain or scenario through a work action or exchange occurrence.".into()
|
||||
},
|
||||
span, line: prefix.bytes().filter(|b| *b == b'\n').count() + 1,
|
||||
column: prefix.rsplit('\n').next().unwrap().chars().count() + 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
(bundle, expected, warnings)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_mixed_read_complete_oa_sa_models_and_all_located_warnings() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
for crlf in [false, true] {
|
||||
let (bundle, expected, warnings) = rich(layer, crlf);
|
||||
assert_eq!(expected.definitions.len(), 4);
|
||||
assert_eq!(warnings.len(), 4);
|
||||
succeeds(&bundle, ProjectLimits::default(), &expected, &warnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calc(alias: &str, number: u128, body: &str) -> String {
|
||||
format!(
|
||||
"calculation {alias} \"Calculation\" id \"{}\" {{ input x id \"{}\" : Number; result Number; body {body}; }}",
|
||||
id(number),
|
||||
id(1)
|
||||
)
|
||||
}
|
||||
|
||||
fn calculation_model(alias: &str, number: u128, body: a::Expr) -> a::Definition {
|
||||
a::Definition {
|
||||
id: id(number),
|
||||
alias: alias.into(),
|
||||
name: "Calculation".into(),
|
||||
description: String::new(),
|
||||
body: a::DefinitionBody::Calculation(a::CalculationDefinition {
|
||||
parameters: vec![a::Parameter {
|
||||
id: a::ParameterId(id(1)),
|
||||
name: "x".into(),
|
||||
value_type: a::AnalysisType::Scalar(a::ScalarType::Number),
|
||||
}],
|
||||
result: a::AnalysisType::Scalar(a::ScalarType::Number),
|
||||
body: a::Binding::Expression(body),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_mixed_read_scoped_uuid_reuse_and_cross_file_call_are_not_global_collisions() {
|
||||
let mut bundle = single("include \"f.arcadia\"; include \"g.arcadia\";");
|
||||
bundle
|
||||
.files
|
||||
.insert("f.arcadia".into(), calc("f", 2, "@g(x: $x)"));
|
||||
bundle.files.insert("g.arcadia".into(), calc("g", 3, "$x"));
|
||||
let mut expected = empty_project();
|
||||
expected.definitions.insert(
|
||||
id(2),
|
||||
calculation_model(
|
||||
"f",
|
||||
2,
|
||||
a::Expr::Call {
|
||||
definition: a::DefinitionRef::Local(id(3)),
|
||||
arguments: BTreeMap::from([(
|
||||
a::ParameterId(id(1)),
|
||||
a::Expr::Parameter(a::ParameterId(id(1))),
|
||||
)]),
|
||||
},
|
||||
),
|
||||
);
|
||||
expected.definitions.insert(
|
||||
id(3),
|
||||
calculation_model("g", 3, a::Expr::Parameter(a::ParameterId(id(1)))),
|
||||
);
|
||||
succeeds(&bundle, ProjectLimits::default(), &expected, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_mixed_read_explicit_identity_classes_versions_and_long_forms() {
|
||||
let empty = single("");
|
||||
succeeds(&empty, ProjectLimits::default(), &empty_project(), &[]);
|
||||
let ordinary = format!("oa_entity e \"Entity\" id \"{}\" {{}}", id(2));
|
||||
let typed = calc("f", 3, "$x");
|
||||
let valid = single(&format!("{ordinary}\n{typed}"));
|
||||
assert!(language::check_mixed_bundle(&valid, ProjectLimits::default()).is_ok());
|
||||
// Each deletion starts from the same valid complete input.
|
||||
for (literal, expected_start) in [
|
||||
(format!("id \"{}\"", id(1)), None),
|
||||
(format!("id \"{}\"", id(2)), Some("oa_entity")),
|
||||
(format!("id \"{}\"", id(3)), Some("calculation")),
|
||||
] {
|
||||
let mut missing = valid.clone();
|
||||
let text = missing.files.get_mut("main.arcadia").unwrap();
|
||||
*text = text.replacen(&literal, "", 1);
|
||||
let start = match expected_start {
|
||||
None => text.find('{').unwrap(),
|
||||
Some("oa_entity") => {
|
||||
text.find("oa_entity").unwrap()
|
||||
+ text[text.find("oa_entity").unwrap()..].find('{').unwrap()
|
||||
}
|
||||
Some(word) => text.find(word).unwrap(),
|
||||
};
|
||||
let errors = fails(&missing, ProjectLimits::default());
|
||||
error_at(
|
||||
&missing,
|
||||
&errors,
|
||||
"main.arcadia",
|
||||
Span {
|
||||
start,
|
||||
end: start + 1,
|
||||
},
|
||||
"invalid_source",
|
||||
"Mixed source8 requires explicit UUIDs.",
|
||||
);
|
||||
}
|
||||
let mut parameter = valid.clone();
|
||||
let text = parameter.files.get_mut("main.arcadia").unwrap();
|
||||
*text = text.replace(&format!("input x id \"{}\"", id(1)), "input x");
|
||||
let start = text.find("calculation").unwrap();
|
||||
error_at(
|
||||
¶meter,
|
||||
&fails(¶meter, ProjectLimits::default()),
|
||||
"main.arcadia",
|
||||
Span {
|
||||
start,
|
||||
end: start + 1,
|
||||
},
|
||||
"invalid_source",
|
||||
"Mixed source8 requires explicit UUIDs.",
|
||||
);
|
||||
for version in ["3", "7", "9"] {
|
||||
let mut bad = empty.clone();
|
||||
*bad.files.get_mut("main.arcadia").unwrap() =
|
||||
document("").replacen("arcadia 8", &format!("arcadia {version}"), 1);
|
||||
error_at(
|
||||
&bad,
|
||||
&fails(&bad, ProjectLimits::default()),
|
||||
"main.arcadia",
|
||||
Span { start: 8, end: 9 },
|
||||
"invalid_source",
|
||||
"Mixed source requires arcadia 8.",
|
||||
);
|
||||
}
|
||||
for word in [
|
||||
"oa_engineering_property",
|
||||
"sa_engineering_property",
|
||||
"oa_analysis_check",
|
||||
"sa_analysis_check",
|
||||
"oa_analysis_case",
|
||||
"sa_analysis_case",
|
||||
] {
|
||||
let bad = single(&format!("{word} v \"Value\" id \"{}\" {{}}", id(2)));
|
||||
let errors = fails(&bad, ProjectLimits::default());
|
||||
let start = bad.files["main.arcadia"].find(word).unwrap();
|
||||
assert_eq!(errors[0].path, "main.arcadia");
|
||||
assert_eq!(errors[0].diagnostic.code, "invalid_source");
|
||||
assert_eq!(
|
||||
errors[0].diagnostic.span,
|
||||
Span {
|
||||
start,
|
||||
end: start + 1
|
||||
}
|
||||
);
|
||||
assert!(
|
||||
errors[0].diagnostic.message.contains("short"),
|
||||
"{errors:#?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn chain() -> SourceBundle {
|
||||
let mut bundle = single("include \"part.arcadia\";");
|
||||
bundle.files.insert(
|
||||
"part.arcadia".into(),
|
||||
"include \"last.arcadia\";\r\n".into(),
|
||||
);
|
||||
bundle.files.insert("last.arcadia".into(), "//é\r\n".into());
|
||||
bundle
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_mixed_read_exact_input_file_edge_and_depth_boundaries() {
|
||||
let empty = single("");
|
||||
assert_eq!(empty.files["main.arcadia"].len(), 78);
|
||||
for cap in [78, 77] {
|
||||
let limits = ProjectLimits {
|
||||
model: ParseLimits {
|
||||
max_source_bytes: cap,
|
||||
..ParseLimits::UNLIMITED
|
||||
},
|
||||
..ProjectLimits::default()
|
||||
};
|
||||
if cap == 78 {
|
||||
succeeds(&empty, limits, &empty_project(), &[]);
|
||||
} else {
|
||||
error_at(
|
||||
&empty,
|
||||
&fails(&empty, limits),
|
||||
"main.arcadia",
|
||||
Span { start: 0, end: 1 },
|
||||
"invalid_source",
|
||||
"Source8 index/output byte limit exceeded.",
|
||||
);
|
||||
}
|
||||
}
|
||||
let bundle = chain();
|
||||
assert_eq!(bundle.files.values().map(String::len).sum::<usize>(), 132);
|
||||
let fits = ProjectLimits {
|
||||
max_files: 3,
|
||||
max_include_edges: 2,
|
||||
max_include_depth: 2,
|
||||
model: ParseLimits {
|
||||
max_source_bytes: 132,
|
||||
..ParseLimits::UNLIMITED
|
||||
},
|
||||
};
|
||||
succeeds(&bundle, fits, &empty_project(), &[]);
|
||||
for (limits, path, span, code, message) in [
|
||||
(
|
||||
ProjectLimits {
|
||||
max_files: 2,
|
||||
..fits
|
||||
},
|
||||
"main.arcadia",
|
||||
Span { start: 0, end: 0 },
|
||||
"project_limit",
|
||||
"Source project exceeds 2 files.",
|
||||
),
|
||||
(
|
||||
ProjectLimits {
|
||||
max_include_edges: 1,
|
||||
..fits
|
||||
},
|
||||
"part.arcadia",
|
||||
Span { start: 0, end: 1 },
|
||||
"project_limit",
|
||||
"Include graph exceeds 1 edges.",
|
||||
),
|
||||
(
|
||||
ProjectLimits {
|
||||
max_include_depth: 1,
|
||||
..fits
|
||||
},
|
||||
"part.arcadia",
|
||||
Span { start: 0, end: 23 },
|
||||
"project_limit",
|
||||
"Include depth exceeds 1.",
|
||||
),
|
||||
(
|
||||
ProjectLimits {
|
||||
model: ParseLimits {
|
||||
max_source_bytes: 131,
|
||||
..fits.model
|
||||
},
|
||||
..fits
|
||||
},
|
||||
"main.arcadia",
|
||||
Span { start: 0, end: 1 },
|
||||
"invalid_source",
|
||||
"Source8 index/output byte limit exceeded.",
|
||||
),
|
||||
] {
|
||||
error_at(&bundle, &fails(&bundle, limits), path, span, code, message);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_mixed_read_paths_missing_cycles_and_detached_files_are_errors() {
|
||||
let good = chain();
|
||||
succeeds(&good, ProjectLimits::default(), &empty_project(), &[]);
|
||||
let mut missing = good.clone();
|
||||
missing.files.remove("last.arcadia");
|
||||
error_at(
|
||||
&missing,
|
||||
&fails(&missing, ProjectLimits::default()),
|
||||
"part.arcadia",
|
||||
Span { start: 0, end: 23 },
|
||||
"missing_include",
|
||||
"Included file 'last.arcadia' is missing.",
|
||||
);
|
||||
let mut detached = good.clone();
|
||||
detached
|
||||
.files
|
||||
.insert("unused.arcadia".into(), "// valid but detached\n".into());
|
||||
error_at(
|
||||
&detached,
|
||||
&fails(&detached, ProjectLimits::default()),
|
||||
"unused.arcadia",
|
||||
Span { start: 0, end: 0 },
|
||||
"unreachable_file",
|
||||
"Supplied source file is not reachable from the entry file.",
|
||||
);
|
||||
let mut cycle = good.clone();
|
||||
cycle
|
||||
.files
|
||||
.insert("last.arcadia".into(), "include \"part.arcadia\";".into());
|
||||
error_at(
|
||||
&cycle,
|
||||
&fails(&cycle, ProjectLimits::default()),
|
||||
"last.arcadia",
|
||||
Span { start: 0, end: 23 },
|
||||
"include_cycle",
|
||||
"Include cycle: part.arcadia -> last.arcadia -> part.arcadia.",
|
||||
);
|
||||
let mut invalid = good.clone();
|
||||
invalid
|
||||
.files
|
||||
.insert("../escape.arcadia".into(), String::new());
|
||||
error_at(
|
||||
&invalid,
|
||||
&fails(&invalid, ProjectLimits::default()),
|
||||
"main.arcadia",
|
||||
Span { start: 0, end: 0 },
|
||||
"invalid_path",
|
||||
"Non-canonical source path '../escape.arcadia'.",
|
||||
);
|
||||
let mut collision = good;
|
||||
collision
|
||||
.files
|
||||
.insert("PART.arcadia".into(), "// detached uppercase\n".into());
|
||||
let errors = fails(&collision, ProjectLimits::default());
|
||||
error_at(
|
||||
&collision,
|
||||
&errors,
|
||||
"main.arcadia",
|
||||
Span { start: 0, end: 0 },
|
||||
"duplicate_path",
|
||||
"Case-colliding source paths 'PART.arcadia' and 'part.arcadia'.",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_mixed_read_final_nested_model_limit_is_distinct_from_syntax_entries() {
|
||||
let bundle = single(&calc("f", 2, "$x"));
|
||||
let mut expected = empty_project();
|
||||
expected.definitions.insert(
|
||||
id(2),
|
||||
calculation_model("f", 2, a::Expr::Parameter(a::ParameterId(id(1)))),
|
||||
);
|
||||
// Two declarations fit both policies. Parameter and expression make four
|
||||
// final records; this does not pretend to set the internal entry quota.
|
||||
let limits = ProjectLimits {
|
||||
model: ParseLimits {
|
||||
max_records: 4,
|
||||
max_graph_objects: 1,
|
||||
..ParseLimits::UNLIMITED
|
||||
},
|
||||
..ProjectLimits::default()
|
||||
};
|
||||
succeeds(&bundle, limits, &expected, &[]);
|
||||
let errors = fails(
|
||||
&bundle,
|
||||
ProjectLimits {
|
||||
model: ParseLimits {
|
||||
max_records: 3,
|
||||
..limits.model
|
||||
},
|
||||
..limits
|
||||
},
|
||||
);
|
||||
error_at(
|
||||
&bundle,
|
||||
&errors,
|
||||
"main.arcadia",
|
||||
Span { start: 0, end: 1 },
|
||||
"model_limit",
|
||||
&format!(
|
||||
"Model exceeds admission limits (1 graph objects, 3 total records, {} ordered references).",
|
||||
usize::MAX
|
||||
),
|
||||
);
|
||||
let errors = fails(
|
||||
&bundle,
|
||||
ProjectLimits {
|
||||
model: ParseLimits {
|
||||
max_graph_objects: 0,
|
||||
..limits.model
|
||||
},
|
||||
..limits
|
||||
},
|
||||
);
|
||||
assert_eq!(errors[0].diagnostic.code, "model_limit");
|
||||
let start = bundle.files["main.arcadia"].find("calculation").unwrap();
|
||||
assert_eq!(
|
||||
errors[0].diagnostic.span,
|
||||
Span {
|
||||
start,
|
||||
end: start + 1
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_mixed_read_typed_reference_caller_allowance_has_a_real_positive_control() {
|
||||
let bundle = single(&calc("f", 2, "$x"));
|
||||
let mut expected = empty_project();
|
||||
expected.definitions.insert(
|
||||
id(2),
|
||||
calculation_model("f", 2, a::Expr::Parameter(a::ParameterId(id(1)))),
|
||||
);
|
||||
let fits = ProjectLimits {
|
||||
model: ParseLimits {
|
||||
max_step_references: 1,
|
||||
..ParseLimits::UNLIMITED
|
||||
},
|
||||
..ProjectLimits::default()
|
||||
};
|
||||
succeeds(&bundle, fits, &expected, &[]);
|
||||
let start = bundle.files["main.arcadia"].find("$x").unwrap() + 1;
|
||||
error_at(
|
||||
&bundle,
|
||||
&fails(
|
||||
&bundle,
|
||||
ProjectLimits {
|
||||
model: ParseLimits {
|
||||
max_step_references: 0,
|
||||
..fits.model
|
||||
},
|
||||
..fits
|
||||
},
|
||||
),
|
||||
"main.arcadia",
|
||||
Span {
|
||||
start,
|
||||
end: start + 1,
|
||||
},
|
||||
"invalid_source",
|
||||
"Source8 references limit exceeded.",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_mixed_read_malformed_duplicate_and_wrong_domain_references() {
|
||||
let calculation = calc("f", 2, "1");
|
||||
let valid = single(&calculation);
|
||||
let mut expected = empty_project();
|
||||
expected.definitions.insert(
|
||||
id(2),
|
||||
calculation_model("f", 2, a::Expr::Number(Exact::from_integer(1))),
|
||||
);
|
||||
succeeds(&valid, ProjectLimits::default(), &expected, &[]);
|
||||
|
||||
let mut malformed = valid.clone();
|
||||
*malformed.files.get_mut("main.arcadia").unwrap() = document(&calc("f", 2, ""));
|
||||
let start = malformed.files["main.arcadia"].find("body ;").unwrap() + 5;
|
||||
error_at(
|
||||
&malformed,
|
||||
&fails(&malformed, ProjectLimits::default()),
|
||||
"main.arcadia",
|
||||
Span {
|
||||
start,
|
||||
end: start + 1,
|
||||
},
|
||||
"invalid_source",
|
||||
"Expected an analysis expression.",
|
||||
);
|
||||
|
||||
let mut unresolved = single("include \"typed.arcadia\";");
|
||||
unresolved
|
||||
.files
|
||||
.insert("typed.arcadia".into(), calc("f", 2, "@missing(x: 1)"));
|
||||
let start = unresolved.files["typed.arcadia"].find("@missing").unwrap() + 1;
|
||||
error_at(
|
||||
&unresolved,
|
||||
&fails(&unresolved, ProjectLimits::default()),
|
||||
"typed.arcadia",
|
||||
Span {
|
||||
start,
|
||||
end: start + 7,
|
||||
},
|
||||
"invalid_source",
|
||||
"Unknown global reference '@missing'.",
|
||||
);
|
||||
|
||||
let ordinary = format!("oa_entity q \"Entity\" id \"{}\" {{}}", id(3));
|
||||
let wrong = single(&format!("{ordinary}\n{}", calc("f", 2, "@q(x: 1)")));
|
||||
let start = wrong.files["main.arcadia"].find("@q").unwrap() + 1;
|
||||
error_at(
|
||||
&wrong,
|
||||
&fails(&wrong, ProjectLimits::default()),
|
||||
"main.arcadia",
|
||||
Span {
|
||||
start,
|
||||
end: start + 1,
|
||||
},
|
||||
"invalid_source",
|
||||
"Wrong reference domain for '@q': Element(Entity, Oa).",
|
||||
);
|
||||
|
||||
let duplicate = single(&format!("{calculation}\n{}", calc("f", 3, "1")));
|
||||
let errors = fails(&duplicate, ProjectLimits::default());
|
||||
let second = calc("f", 3, "1");
|
||||
let span = authored_span(&duplicate.files["main.arcadia"], &second);
|
||||
error_at(
|
||||
&duplicate,
|
||||
&errors,
|
||||
"main.arcadia",
|
||||
span,
|
||||
"invalid_source",
|
||||
"Duplicate global alias.",
|
||||
);
|
||||
assert_eq!(errors[0].related.len(), 1);
|
||||
assert_eq!(errors[0].related[0].path, "main.arcadia");
|
||||
assert_eq!(
|
||||
errors[0].related[0].span,
|
||||
authored_span(&duplicate.files["main.arcadia"], &calculation)
|
||||
);
|
||||
|
||||
let second = calc("g", 2, "1");
|
||||
let duplicate_id = single(&format!("{calculation}\n{second}"));
|
||||
error_at(
|
||||
&duplicate_id,
|
||||
&fails(&duplicate_id, ProjectLimits::default()),
|
||||
"main.arcadia",
|
||||
authored_span(&duplicate_id.files["main.arcadia"], &second),
|
||||
"invalid_source",
|
||||
"Duplicate global identity.",
|
||||
);
|
||||
|
||||
let duplicate_parameter = calc("f", 2, "$x").replace(
|
||||
"result Number;",
|
||||
&format!("input x id \"{}\" : Number; result Number;", id(4)),
|
||||
);
|
||||
let bad = single(&duplicate_parameter);
|
||||
let at = bad.files["main.arcadia"].rfind("input x").unwrap() + 6;
|
||||
error_at(
|
||||
&bad,
|
||||
&fails(&bad, ProjectLimits::default()),
|
||||
"main.arcadia",
|
||||
Span {
|
||||
start: at,
|
||||
end: at + 1,
|
||||
},
|
||||
"invalid_source",
|
||||
"Duplicate parameter name.",
|
||||
);
|
||||
let second_parameter = format!("input y id \"{}\" : Number;", id(1));
|
||||
let duplicate_parameter_id = calc("f", 2, "$x").replace(
|
||||
"result Number;",
|
||||
&format!("{second_parameter} result Number;"),
|
||||
);
|
||||
let bad = single(&duplicate_parameter_id);
|
||||
let at = authored_span(&bad.files["main.arcadia"], &second_parameter).start + 11;
|
||||
error_at(
|
||||
&bad,
|
||||
&fails(&bad, ProjectLimits::default()),
|
||||
"main.arcadia",
|
||||
Span {
|
||||
start: at,
|
||||
end: at + 1,
|
||||
},
|
||||
"invalid_source",
|
||||
"Duplicate parameter identity.",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_mixed_read_unicode_columns_are_not_utf8_byte_columns() {
|
||||
let source = "arcadia 8;\r\nproject p \"港🌊\" id \"00000000-0000-0000-0000-000000000001\" { oa_activity w \"Work\" id \"00000000-0000-0000-0000-000000000002\" {} }\r\n";
|
||||
let bundle = SourceBundle {
|
||||
entry: "main.arcadia".into(),
|
||||
files: BTreeMap::from([("main.arcadia".into(), source.into())]),
|
||||
};
|
||||
let mut expected = empty_project();
|
||||
expected.name = "港🌊".into();
|
||||
expected.elements.insert(
|
||||
id(2),
|
||||
Element {
|
||||
id: id(2),
|
||||
alias: "w".into(),
|
||||
name: "Work".into(),
|
||||
description: String::new(),
|
||||
parent: None,
|
||||
body: ElementBody::Oa(OaBody::Activity),
|
||||
},
|
||||
);
|
||||
let warnings = [
|
||||
("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: "main.arcadia".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: Span { start: 76, end: 141 }, line: 2, column: 60 },
|
||||
});
|
||||
succeeds(&bundle, ProjectLimits::default(), &expected, &warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_mixed_read_ordinary_parity_uses_an_independent_complete_model() {
|
||||
let b = single(&format!(
|
||||
"oa_entity team \"Team\" id \"{}\" {{ oa_actor sailor \"Sailor\" id \"{}\" {{ description \"海\"; }} }}",
|
||||
id(2),
|
||||
id(3)
|
||||
));
|
||||
let mut expected = empty_project();
|
||||
for (n, alias, name, description, parent, body) in [
|
||||
(2, "team", "Team", "", None, ElementBody::Oa(OaBody::Entity)),
|
||||
(
|
||||
3,
|
||||
"sailor",
|
||||
"Sailor",
|
||||
"海",
|
||||
Some(id(2)),
|
||||
ElementBody::Oa(OaBody::Actor),
|
||||
),
|
||||
] {
|
||||
expected.elements.insert(
|
||||
id(n),
|
||||
Element {
|
||||
id: id(n),
|
||||
alias: alias.into(),
|
||||
name: name.into(),
|
||||
description: description.into(),
|
||||
parent,
|
||||
body,
|
||||
},
|
||||
);
|
||||
}
|
||||
succeeds(&b, ProjectLimits::default(), &expected, &[]);
|
||||
let ordinary = language::parse_with_report(&b.files["main.arcadia"]).unwrap();
|
||||
assert!(ordinary.migration.is_none());
|
||||
assert_eq!(ordinary.project, expected);
|
||||
let compiled = source_project::compile_bundle(&b, ProjectLimits::default()).unwrap();
|
||||
assert_eq!(compiled.decoded.project, expected);
|
||||
assert_eq!(compiled.bundle, b);
|
||||
}
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
@@ -633,6 +633,23 @@ a { text-decoration: none; }
|
||||
<dd class="text-suffix"><p><span class="docutils literal">.arcadia</span></p>
|
||||
</dd>
|
||||
</dl>
|
||||
<section id="staged-source8-rust-read-api">
|
||||
<h2>Staged source8 Rust read API</h2>
|
||||
<p>The core library separately offers <span class="docutils literal"><span class="pre">language::check_mixed_bundle(&SourceBundle,</span> ProjectLimits)</span> for complete ordinary/typed source8 validation. Its opaque
|
||||
<span class="docutils literal">CheckedMixed</span> result borrows the unchanged input and exposes only <span class="docutils literal">project()</span>
|
||||
and <span class="docutils literal">warnings()</span>. Explicit UUIDs are required for the project, every ordinary
|
||||
and typed declaration, and typed parameters; no IDs are generated. All supplied
|
||||
canonical files must be reachable from the entry. Errors return no admitted
|
||||
model; success preserves actual located warnings and the complete model.</p>
|
||||
<p>This bounded API selects both retained text and entry producer checks and carries
|
||||
the caller's whole-project policy through admission. It does not promise an
|
||||
all-heap memory bound. It performs no filesystem discovery, migration, formatting,
|
||||
source stabilization, indexing, saving or history installation. Existing ordinary
|
||||
parse/compile/update APIs, CLI commands, desktop and server flows remain separate;
|
||||
a read result does not grant their typed editing or save authority. Generic and
|
||||
typed depth policies and platform qualification limits remain unchanged. This
|
||||
staged capability does not replace the broader conformance obligations below.</p>
|
||||
</section>
|
||||
<section id="scope-and-conformance">
|
||||
<h2>1. Scope and conformance</h2>
|
||||
<p>This specification defines the shared Operational Analysis (OA) and System
|
||||
|
||||
@@ -6,6 +6,26 @@ Arcadia Studio OA / SA Language and Editing Contract
|
||||
:Core implementation: ``crates/oa-core``
|
||||
:Text suffix: ``.arcadia``
|
||||
|
||||
Staged source8 Rust read API
|
||||
----------------------------------------
|
||||
|
||||
The core library separately offers ``language::check_mixed_bundle(&SourceBundle,
|
||||
ProjectLimits)`` for complete ordinary/typed source8 validation. Its opaque
|
||||
``CheckedMixed`` result borrows the unchanged input and exposes only ``project()``
|
||||
and ``warnings()``. Explicit UUIDs are required for the project, every ordinary
|
||||
and typed declaration, and typed parameters; no IDs are generated. All supplied
|
||||
canonical files must be reachable from the entry. Errors return no admitted
|
||||
model; success preserves actual located warnings and the complete model.
|
||||
|
||||
This bounded API selects both retained text and entry producer checks and carries
|
||||
the caller's whole-project policy through admission. It does not promise an
|
||||
all-heap memory bound. It performs no filesystem discovery, migration, formatting,
|
||||
source stabilization, indexing, saving or history installation. Existing ordinary
|
||||
parse/compile/update APIs, CLI commands, desktop and server flows remain separate;
|
||||
a read result does not grant their typed editing or save authority. Generic and
|
||||
typed depth policies and platform qualification limits remain unchanged. This
|
||||
staged capability does not replace the broader conformance obligations below.
|
||||
|
||||
1. Scope and conformance
|
||||
------------------------
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Public mixed read implementation
|
||||
|
||||
Base7b06b8ea, selected root contract bcddaba3. Public
|
||||
language::check_mixed_bundle returns opaque non-Clone CheckedMixed borrowing the
|
||||
complete original SourceBundle, with only immutable project/warnings accessors.
|
||||
Its private resolved field retains syntax, expression/occurrence vectors, ledger
|
||||
and admission evidence; there is no index, output, save or extraction authority.
|
||||
|
||||
The shared parser mixed hooks now compile in production. Ordinary callers still
|
||||
select None. The fresh constructor binds the actual bundle and exact ProjectLimits,
|
||||
derives existing internal policy once, selects Some(Borrowed) with zero carry and
|
||||
both producers, then consumes syntax through complete resolution/admission.
|
||||
The borrowed guard retains the actual source reference even when test-only
|
||||
Pending/Retained variants are absent. No duplicate live policy/model is added.
|
||||
Public schema/UUID requirement wording replaces private-staging wording only;
|
||||
all limiter phase/category/span transport remains inherited.
|
||||
|
||||
Private legacy index/update/write, ordinary-index/rename/emission/acceptance and
|
||||
fixture/pending constructors remain test-only. Shared parse/lower code remains
|
||||
private. Necessary retained fields have individual documented non-test dead-code
|
||||
allowances; no module-wide suppression, fake read, or new public lint-only API.
|
||||
Exact production compiler lint findings remain a parent gate, not inferred clean
|
||||
from source inspection. Existing298 tests are not edited.
|
||||
|
||||
One internal public_read test invokes the actual production parse helper and
|
||||
observes borrowedC0, both producer inventories, every inherited/derived policy
|
||||
field (including262144 entries independent of model records1), full empty-Project
|
||||
and warning parity, Usage/numeric parity and unchanged input. Three rustdoc blocks
|
||||
provide a compiling real usage example plus source-drop and source-mutation
|
||||
compile-fail cases with later result use. Independent worker owns the non-test
|
||||
mixed_source8_read integration target and its separate complete-model oracles.
|
||||
|
||||
Established docs/language.rst gains a bounded staged-read note without changing
|
||||
its broader conformance/version obligations. Existing docutils HTML5 generation
|
||||
with halt/report level2 regenerated language.html successfully. An initial local
|
||||
RST generation refused the new section formatting; the section separator and
|
||||
blank line were corrected before successful regeneration. No Rust compiler,
|
||||
Cargo test, CLI, debugger or publication was run by this worker.
|
||||
|
||||
Parent owns pinned compilation, all old/new runtime tests, doctests, strict
|
||||
Clippy, formatting disposition and production Parser stack regression checks.
|
||||
DEPTH_128_VALIDATED=false, existing depth policies, logical-accounting exclusions,
|
||||
full-pipeline limitation and deferred Linux qualification remain unchanged.
|
||||
## First non-test compile correction
|
||||
|
||||
Parent's integration-list build at ddf823a stopped with E0063: the semantic review
|
||||
row-key reader's Parser initializer still gated mixed:None under cfg(test).
|
||||
That initializer now supplies None in production too, preserving its ordinary
|
||||
facet-reading behavior. Source inventory found the other shared Parser initializers
|
||||
in language.rs and mixed.rs already coherent; language::legacy uses its own
|
||||
separate Parser type. No API, test or runtime policy changed. The failed build
|
||||
record remains preserved; no compiler was run for this repair by the worker.
|
||||
## Non-test integration result and warning cleanup
|
||||
|
||||
Parent's actual non-test integration suite at 8555e417 passed all ten tests with
|
||||
the independent complete-model and diagnostic assertions unchanged. Compilation
|
||||
reported an unused Budget::limits observer and a redundant private language self
|
||||
import shadowing the integration helper's public glob export. The observer is
|
||||
now cfg(test), matching all real callers; the integration import drops only self.
|
||||
No semantic implementation, policy, fixture or expected result changed. Parent
|
||||
explicitly authorized that one test import-line edit after its independent worker
|
||||
finished. This is warning cleanup, not a claim that later lint/full/doctest gates
|
||||
already passed. No compiler was run by this worker.
|
||||
@@ -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