Compare commits

...
12 changed files with 2161 additions and 102 deletions
+12 -12
View File
@@ -4,12 +4,12 @@
- Follow `PROJECT_GOAL.md` and the user's current instructions. Work autonomously within scope. Resolve discoverable questions yourself; record, test, and revise reversible assumptions. Ask only when a consequential decision needs information or authorization not already available. Continue independent useful work while blocked elsewhere.
- Map unfamiliar domains through primary sources, comparable systems, and real user workflows. Identify underlying needs, valuable concepts, and avoidable friction; evaluate improvements rather than merely reproducing features. Synthesize findings into decisions or experiments, and stop research when another action offers greater value.
- Make specifications precise enough for compatible independent implementations: definitions, relationships, invariants, valid/invalid examples, and lifecycle or transformation rules where relevant. Trace important requirements and decisions to implementation and verification. Resolve specification ambiguity at its source when implementation exposes it.
- Make specifications precise enough for compatible independent implementations: definitions, relationships, invariants, valid / invalid examples, and lifecycle or transformation rules where relevant. Trace important requirements and decisions to implementation and verification. Resolve specification ambiguity at its source when implementation exposes it.
- For consequential uncertain choices, compare materially different candidates against explicit user-derived criteria. Ask independent critics to challenge assumptions and attempt to falsify proposals; refine or replace candidates as evidence warrants. Record the choice, evidence, tradeoffs, rejected approaches, and reasons to revisit it. Use proportionate effort for reversible local choices.
- Plan milestones that resolve uncertainty or deliver useful capability, with explicit dependencies. Test foundational assumptions before substantial downstream work. At checkpoints, assess progress toward the actual objective and replan when evidence invalidates the strategy or progress stalls.
- Define observable success and suitable evaluators before repeated improvement: invariants, examples, round trips, transformations, performance checks, realistic user tasks, or explicit review rubrics. Ensure the evaluator measures the intended outcome. For experiments, record the question, expectations, method, result, and implications; isolate exploratory prototypes from production architecture.
- Build and run meaningful changes, exercise actual behavior, and inspect useful logs. Investigate crashes, serious warnings, corrupted state, and interaction defects. For GUI work, use Computer Use or equivalent tools to inspect realistic workflows, cognitive load, editing/selection, navigation, feedback, recovery, loading/empty/failure states, accessibility, layout, focus/disabled states, and responsiveness.
- Match independent reviews to the artifact and risks. For languages or formats, include ambiguity, round trips, human/AI editability, and version-control behavior where relevant. Verify findings, fix the appropriate conceptual, specification, architectural, implementation, or interaction layer, and recheck affected dependents. Reuse valid evidence; repeat checks for concrete risks, changes, findings, or required gates. Compilation, screenshots, and worker reports alone do not establish correctness or usability.
- Build and run meaningful changes, exercise actual behavior, and inspect useful logs. Investigate crashes, serious warnings, corrupted state, and interaction defects. For GUI work, use Computer Use or equivalent tools to inspect realistic workflows, cognitive load, editing / selection, navigation, feedback, recovery, loading / empty / failure states, accessibility, layout, focus / disabled states, and responsiveness.
- Match independent reviews to the artifact and risks. For languages or formats, include ambiguity, round trips, human / AI editability, and version-control behavior where relevant. Verify findings, fix the appropriate conceptual, specification, architectural, implementation, or interaction layer, and recheck affected dependents. Reuse valid evidence; repeat checks for concrete risks, changes, findings, or required gates. Compilation, screenshots, and worker reports alone do not establish correctness or usability.
- Favor the simplest maintainable design satisfying known requirements and justified extensibility. Avoid speculative abstraction, unrelated refactoring, and repeating approaches already invalidated by evidence.
## User input
@@ -24,10 +24,10 @@ Keep one authoritative home for each kind of information. Use links and brief sc
| Information | Authoritative home |
| --- | --- |
| Live plans, priorities, dependencies, ownership, status, blockers, and handoffs | Gitea issues/milestones; linked PRs hold change review and validation. |
| Specifications, architecture, interfaces, and build/run documentation | Established version-controlled project documents. |
| Live plans, priorities, dependencies, ownership, status, blockers, and handoffs | Gitea issues / milestones; linked PRs hold change review and validation. |
| Specifications, architecture, interfaces, and build / run documentation | Established version-controlled project documents. |
| Durable decisions and reusable evidence | `harness/DECISIONS.md` and relevant topic notes, or existing equivalents. |
| Navigation and integrated-state checkpoint | `harness/STATE.md`: links, verified code commit/release, capabilities, and material limitations. No live task ledger. |
| Navigation and integrated-state checkpoint | `harness/STATE.md`: links, verified code commit / release, capabilities, and material limitations. No live task ledger. |
| Shared entrypoint | Gitea Wiki: overview and links, without duplicating versioned documentation. |
| Published deliverables | Gitea releases, tied to source commits and verification. |
@@ -38,9 +38,9 @@ Use Gitea for live coordination and the relevant code version for implementation
All independent agents coordinating through Gitea run on completely separate machines, each with its own clone, filesystem, tools, processes, and runtime. Gitea and pushed Git history are the shared communication and delivery channels. Local files (including `harness`), unpushed commits, worktrees, test results, and localhost services are not accessible to peers. Matching branch names or paths do not imply synchronized contents.
1. On starting or resuming, including after compaction, inspect the working branch, local changes, applicable instructions, relevant shared records, and existing implementation. Recover context from authoritative records. Check for newer shared instructions on the actual remote default branch without overwriting local work. Read what is relevant; avoid repeated full-history scans and constant polling.
2. Search before creating tasks. Claim a meaningful outcome in its Gitea issue with a stable session identity, scope, remote repository/branch, and acceptance criteria. Keep small steps within that task. Recheck after claiming; comments are not atomic locks. Respect existing ownership and resolve overlapping claims before overlapping work.
3. Independent sessions are peers. Each owns its contribution and delegated work on its own machine. Record one integration/release owner when a milestone needs shared coordination. Use distinct remote work branches, partition files/interfaces, and coordinate changes to shared specifications and documents. Worktrees provide local isolation only; they do not share work between machines.
4. At meaningful checkpoints or handoffs, record changed results, commit/PR links, verification commands and environment, blockers, and next actions. Identify the tested code version and label uncommitted or unpublished work as local-only. Before asking peers to consume results, push the required code and versioned documentation, including relevant `harness` updates; publish other required artifacts in Gitea and link them. Use repository-relative paths with commit references, reproducible commands, and necessary environment details; local paths and localhost URLs alone are not handoffs. Do not assume another agent is running or has read a message; silence alone does not establish abandonment. Record evidence and preserve existing work when reassigning a task.
2. Search before creating tasks. Claim a meaningful outcome in its Gitea issue with a stable session identity, scope, remote repository / branch, and acceptance criteria. Keep small steps within that task. Recheck after claiming; comments are not atomic locks. Respect existing ownership and resolve overlapping claims before overlapping work.
3. Independent sessions are peers. Each owns its contribution and delegated work on its own machine. Record one integration / release owner when a milestone needs shared coordination. Use distinct remote work branches, partition files / interfaces, and coordinate changes to shared specifications and documents. Worktrees provide local isolation only; they do not share work between machines.
4. At meaningful checkpoints or handoffs, record changed results, commit / PR links, verification commands and environment, blockers, and next actions. Identify the tested code version and label uncommitted or unpublished work as local-only. Before asking peers to consume results, push the required code and versioned documentation, including relevant `harness` updates; publish other required artifacts in Gitea and link them. Use repository-relative paths with commit references, reproducible commands, and necessary environment details; local paths and localhost URLs alone are not handoffs. Do not assume another agent is running or has read a message; silence alone does not establish abandonment. Record evidence and preserve existing work when reassigning a task.
5. Follow project review and merge conventions. Fetch the relevant remote branches and inspect the identified commits before reviewing or integrating another machine's work. Resolve conflicts while preserving others' edits, and verify the combined result locally; report material environment differences. Local instruction-file edits reach peers only after publication and retrieval. Do not overwrite others' work or force-push shared history to resolve coordination problems.
6. Close work when its acceptance criteria are met and the result is integrated or delivered as appropriate. Mark duplicates and superseded tasks explicitly with replacement links. Then choose further useful unowned work while the requested objective remains incomplete.
@@ -64,10 +64,10 @@ Preserve concise records of decisions and rationale, useful experiments, failed
Consolidate duplicates, repair stale guidance, and remove obsolete temporary files alongside useful project work. Before removing material, check references, active ownership, and ongoing use; preserve unique knowledge and update affected links. Prefer closing, superseding, or archiving history over deletion. User input is excluded from autonomous cleanup.
If legacy local task ledgers or plans duplicate Gitea, migrate their live content once into existing or appropriately scoped issues, preserve useful decisions/evidence, and mark the old records superseded. Record the migration so later sessions do not repeat it. Do not create empty documents or maintain records for ceremony.
If legacy local task ledgers or plans duplicate Gitea, migrate their live content once into existing or appropriately scoped issues, preserve useful decisions / evidence, and mark the old records superseded. Record the migration so later sessions do not repeat it. Do not create empty documents or maintain records for ceremony.
## Delivery
Publish versioned Gitea releases at meaningful working milestones; mark unfinished versions as prereleases. Check existing releases/tags and coordinate one owner and version before publishing. Build from an identified integrated commit and verify the packaged result and launch steps.
Publish versioned Gitea releases at meaningful working milestones; mark unfinished versions as prereleases. Check existing releases / tags and coordinate one owner and version before publishing. Build from an identified integrated commit and verify the packaged result and launch steps.
Include runnable builds or appropriate artifacts, concise release notes, build/run/deployment instructions as applicable, known limitations, tested platforms, and verification evidence. Confirm publication and artifact availability. Use existing authorized API access or project tooling when MCP lacks a needed release/upload operation. Do not silently replace published versions with different contents.
Include runnable builds or appropriate artifacts, concise release notes, build / run / deployment instructions as applicable, known limitations, tested platforms, and verification evidence. Confirm publication and artifact availability. Use existing authorized API access or project tooling when MCP lacks a needed release / upload operation. Do not silently replace published versions with different contents.
@@ -237,55 +237,242 @@ impl<'s> Index<'s> {
for element in lowered.elements.values() {
insert_name(Target::Global(element.id), &element.alias)?;
}
let mut index = Self {
let original = Original {
kind: OriginalKind::Typed { sources, witnesses },
at: std::cell::Cell::new(None),
};
let data = IndexData::build(
names,
units
.iter()
.flat_map(|u| u.declarations.iter().map(move |d| (u.file, d))),
&lowered.expressions,
&lowered.occurrences,
&original,
budget,
&mut Storage(None),
)?;
Ok(Self {
sources,
names: data.names,
occurrences: data.occurrences,
expressions: data.expressions,
})
}
}
use super::mixed::project::{
IndexInput,
accounting::{Ledger, Slots},
};
/// Owned maps shared by the two nominal provenance wrappers. No edit authority.
#[derive(Debug)]
pub(super) struct IndexData {
pub(super) names: BTreeMap<Target, String>,
pub(super) occurrences: BTreeMap<OccurrenceKey, Location>,
pub(super) expressions: BTreeMap<ExpressionKey, Location>,
}
// Only project::index can mint IndexInput: its private seal is never exported.
pub(super) fn mixed_data(
input: IndexInput<'_>,
budget: &mut Budget,
ledger: &mut Ledger,
) -> Result<IndexData, lower::Failure> {
let original = Original {
kind: OriginalKind::Mixed(&input),
at: std::cell::Cell::new(None),
};
let mut storage = Storage(Some(ledger));
let result = (|| {
budget.active()?;
let mut names = BTreeMap::new();
let mut insert = |target, name: &str| -> Result<(), Error> {
storage.reserve(budget, 1, name.len(), Span::default())?;
if names.insert(target, name.to_owned()).is_some() {
return Err(error(
Span::default(),
"Duplicate qualified catalog identity.",
));
}
Ok(())
};
let p = input.project;
insert(Target::Global(p.id), &p.alias)?;
for d in p.definitions.values() {
insert(Target::Global(d.id), &d.alias)?;
if let a::DefinitionBody::Calculation(c) = &d.body {
for parameter in &c.parameters {
insert(
Target::Parameter {
calculation: d.id,
parameter: parameter.id,
},
&parameter.name,
)?;
}
}
}
for v in p.elements.values() {
insert(Target::Global(v.id), &v.alias)?;
}
for v in p.relations.values() {
insert(Target::Global(v.id), &v.alias)?;
}
for v in p.diagrams.values() {
insert(Target::Global(v.id), &v.alias)?;
for n in v.nodes.values() {
insert(Target::Global(n.id), &n.alias)?;
}
}
for v in p.artifact_links.values() {
insert(Target::Global(v.id), &v.alias)?;
}
for v in p.traces.values() {
insert(Target::Global(v.id), &v.alias)?;
}
IndexData::build(
names,
input.declarations(),
input.expressions,
input.occurrences,
&original,
budget,
&mut storage,
)
})();
result.map_err(|error| lower::Failure {
file: original.at.get(),
error,
})
}
struct Storage<'a>(Option<&'a mut Ledger>);
impl Storage<'_> {
fn reserve(
&mut self,
budget: &Budget,
entries: usize,
text: usize,
at: Span,
) -> Result<(), Error> {
if let Some(ledger) = &mut self.0 {
ledger.reserve(
budget,
Slots {
entries,
occurrences: 0,
text,
},
at,
)?;
}
Ok(())
}
fn release(&mut self, entries: usize) {
if let Some(ledger) = &mut self.0 {
ledger.release(Slots::entries(entries));
}
}
}
struct Original<'a> {
kind: OriginalKind<'a>,
at: std::cell::Cell<Option<usize>>,
}
enum OriginalKind<'a> {
Typed {
sources: &'a BTreeMap<usize, String>,
witnesses: &'a BTreeMap<usize, Vec<lex::Token>>,
},
Mixed(&'a IndexInput<'a>),
}
impl Original<'_> {
fn checked(&self, location: Location) -> Result<&str, Error> {
self.at.set(Some(location.file));
if location.span.start >= location.span.end {
return Err(error(location.span, "Empty or reversed source span."));
}
let source = match &self.kind {
OriginalKind::Typed { sources, .. } => sources.get(&location.file).map(String::as_str),
OriginalKind::Mixed(input) => input.source(location.file),
};
source
.and_then(|s| s.get(location.span.start..location.span.end))
.ok_or_else(|| {
error(
location.span,
"Source span is outside its original UTF-8 file.",
)
})
}
fn tokens(&self, file: usize) -> Option<&[lex::Token]> {
match &self.kind {
OriginalKind::Typed { witnesses, .. } => witnesses.get(&file).map(Vec::as_slice),
OriginalKind::Mixed(input) => input.tokens(file),
}
}
}
impl IndexData {
fn build<'a>(
names: BTreeMap<Target, String>,
declarations: impl Iterator<Item = (usize, &'a declarations::Declaration)> + Clone,
expressions: &[lower::ExpressionSite],
occurrences: &[lower::Occurrence],
sources: &Original<'_>,
budget: &mut Budget,
storage: &mut Storage<'_>,
) -> Result<Self, Error> {
let mut index = Self {
names,
occurrences: BTreeMap::new(),
expressions: BTreeMap::new(),
};
let mut owners = BTreeMap::new();
let mut physical = BTreeSet::new();
for unit in units {
for declaration in &unit.declarations {
let owner = declaration.header.identity.value.ok_or_else(|| {
error(
declaration.span,
"Missing ID requires source stabilization.",
)
})?;
let location = Location {
file: unit.file,
span: declaration.span,
};
checked_source(sources, location)?;
budget.lowering_entries(owners.len() + 1, declaration.span)?;
if owners.insert(owner, location).is_some() {
return Err(error(declaration.span, "Duplicate source owner."));
}
let key = OccurrenceKey {
owner,
field: None,
path: None,
role: TokenRole::Declaration,
target: Target::Global(owner),
};
index.insert(
key,
Location {
file: unit.file,
span: declaration.header.alias.span,
},
witnesses,
&mut physical,
budget,
)?;
for (file, declaration) in declarations.clone() {
sources.at.set(Some(file));
let owner = declaration.header.identity.value.ok_or_else(|| {
error(
declaration.span,
"Missing ID requires source stabilization.",
)
})?;
let location = Location {
file,
span: declaration.span,
};
sources.checked(location)?;
budget.lowering_entries(owners.len() + 1, declaration.span)?;
storage.reserve(budget, 1, 0, declaration.span)?;
if owners.insert(owner, location).is_some() {
return Err(error(declaration.span, "Duplicate source owner."));
}
let key = OccurrenceKey {
owner,
field: None,
path: None,
role: TokenRole::Declaration,
target: Target::Global(owner),
};
index.insert(
key,
Location {
file,
span: declaration.header.alias.span,
},
sources,
&mut physical,
budget,
storage,
)?;
}
// Parent-before-child is part of the lowering contract. One forward
// pass rejects forward, cyclic, cross-file/owner/field and duplicate
// paths. Each node allocates one shared edge, not a copied path vector.
let mut paths: Vec<(ExpressionKey, usize)> = Vec::new();
for (position, site) in lowered.expressions.iter().enumerate() {
for (position, site) in expressions.iter().enumerate() {
sources.at.set(Some(site.file));
if !matches!(
site.field,
Field::Body | Field::Base | Field::Predicate | Field::Override(_)
@@ -299,7 +486,7 @@ impl<'s> Index<'s> {
file: site.file,
span: site.span,
};
checked_source(sources, location)?;
sources.checked(location)?;
let owner = owners
.get(&site.owner)
.ok_or_else(|| error(site.span, "Unknown expression owner."))?;
@@ -311,7 +498,7 @@ impl<'s> Index<'s> {
"Expression parent must precede its child.",
));
}
let ancestor = &lowered.expressions[parent];
let ancestor = &expressions[parent];
if ancestor.owner != site.owner
|| ancestor.field != site.field
|| ancestor.file != site.file
@@ -336,6 +523,7 @@ impl<'s> Index<'s> {
}
let depth = paths[parent].1 + 1;
budget.frame(depth, site.span)?;
storage.reserve(budget, 1, 0, site.span)?;
(
Path(Some(Arc::new(Step {
parent: paths[parent].0.path.clone(),
@@ -352,12 +540,14 @@ impl<'s> Index<'s> {
field: site.field,
path,
};
storage.reserve(budget, 2, 0, site.span)?;
if index.expressions.insert(key.clone(), location).is_some() {
return Err(error(site.span, "Duplicate qualified expression path."));
}
paths.push((key, depth));
}
for occurrence in &lowered.occurrences {
for occurrence in occurrences {
sources.at.set(Some(occurrence.file));
let location = Location {
file: occurrence.file,
span: occurrence.span,
@@ -368,8 +558,7 @@ impl<'s> Index<'s> {
contained(*owner, location)?;
let path = match occurrence.expression {
Some(site) => {
let expression = lowered
.expressions
let expression = expressions
.get(site)
.ok_or_else(|| error(occurrence.span, "Unknown occurrence expression."))?;
if expression.owner != occurrence.owner
@@ -420,20 +609,28 @@ impl<'s> Index<'s> {
role: TokenRole::Use(occurrence.role),
target: occurrence.target,
};
index.insert(key, location, witnesses, &mut physical, budget)?;
index.insert(key, location, sources, &mut physical, budget, storage)?;
}
let owner_count = owners.len();
let physical_count = physical.len();
drop(owners);
drop(physical);
storage.release(owner_count + physical_count);
// Argument labels belong to this precise call site and its callee, not
// merely to a parameter UUID/name present somewhere in the catalog.
let mut calls = BTreeMap::new();
for (key, at) in &index.occurrences {
sources.at.set(Some(at.file));
if key.role == TokenRole::Use(Role::CallTarget) {
let site = (key.owner, key.field, key.path.clone());
storage.reserve(budget, 1, 0, at.span)?;
if calls.insert(site, key.target).is_some() {
return Err(error(at.span, "Multiple callees at one expression site."));
}
}
}
for (key, at) in &index.occurrences {
sources.at.set(Some(at.file));
if let (TokenRole::Use(Role::ArgumentLabel), Target::Parameter { calculation, .. }) =
(key.role, key.target)
&& calls.get(&(key.owner, key.field, key.path.clone()))
@@ -445,7 +642,8 @@ impl<'s> Index<'s> {
));
}
}
for (position, site) in lowered.expressions.iter().enumerate() {
for (position, site) in expressions.iter().enumerate() {
sources.at.set(Some(site.file));
if let Some((parent, Child::Argument(parameter))) = site.parent {
let key = &paths[parent].0;
let Some(Target::Global(calculation)) =
@@ -468,7 +666,12 @@ impl<'s> Index<'s> {
}
}
}
index.check_syntax_edges(units, lowered, budget)?;
let path_count = paths.len();
let call_count = calls.len();
drop(paths);
drop(calls);
storage.release(path_count + call_count);
index.check_syntax_edges(declarations, expressions, sources, budget, storage)?;
Ok(index)
}
@@ -477,12 +680,16 @@ impl<'s> Index<'s> {
/// with indexed span lookup; it does not lex or copy source/path strings.
fn check_syntax_edges<'a>(
&self,
units: &'a [Unit],
lowered: &Lowered,
declarations: impl Iterator<Item = (usize, &'a declarations::Declaration)> + Clone,
expressions: &[lower::ExpressionSite],
sources: &Original<'_>,
budget: &Budget,
storage: &mut Storage<'_>,
) -> Result<(), Error> {
let mut globals = BTreeMap::new();
let mut parameters = BTreeMap::new();
sources.at.set(None);
storage.reserve(budget, self.names.len(), 0, Span::default())?;
for (target, name) in &self.names {
match target {
Target::Global(id) => {
@@ -497,7 +704,9 @@ impl<'s> Index<'s> {
}
}
let mut sites = BTreeMap::new();
for (position, site) in lowered.expressions.iter().enumerate() {
for (position, site) in expressions.iter().enumerate() {
sources.at.set(Some(site.file));
storage.reserve(budget, 1, 0, site.span)?;
if sites
.insert(
(
@@ -518,39 +727,43 @@ impl<'s> Index<'s> {
let mut root = |file, owner, field, binding: &'a super::Binding| -> Result<(), Error> {
if let super::Binding::Expression(expression) = binding {
budget.lowering_entries(work.len() + 1, expression.span)?;
sources.at.set(Some(file));
storage.reserve(budget, 1, 0, expression.span)?;
work.push((file, owner, field, expression, None));
}
Ok(())
};
for unit in units {
for declaration in &unit.declarations {
let owner = declaration.header.identity.value.expect("validated owner");
match &declaration.body {
declarations::Body::Calculation { body, .. } => {
root(unit.file, owner, Field::Body, &body.value)?
}
declarations::Body::Property { base, .. } => {
root(unit.file, owner, Field::Base, &base.value)?
}
declarations::Body::Check { predicate, .. } => {
root(unit.file, owner, Field::Predicate, &predicate.value)?
}
declarations::Body::Case { overrides, .. } => {
for entry in &overrides.value {
let target =
*globals.get(entry.target.name.as_str()).ok_or_else(|| {
error(entry.target.span, "Unknown syntax override target.")
})?;
root(unit.file, owner, Field::Override(target), &entry.binding)?;
}
}
_ => {}
for (file, declaration) in declarations.clone() {
sources.at.set(Some(file));
let owner = declaration.header.identity.value.expect("validated owner");
match &declaration.body {
declarations::Body::Calculation { body, .. } => {
root(file, owner, Field::Body, &body.value)?
}
declarations::Body::Property { base, .. } => {
root(file, owner, Field::Base, &base.value)?
}
declarations::Body::Check { predicate, .. } => {
root(file, owner, Field::Predicate, &predicate.value)?
}
declarations::Body::Case { overrides, .. } => {
for entry in &overrides.value {
let target = *globals.get(entry.target.name.as_str()).ok_or_else(|| {
error(entry.target.span, "Unknown syntax override target.")
})?;
root(file, owner, Field::Override(target), &entry.binding)?;
}
}
_ => {}
}
}
let mut visited = 0usize;
while let Some((file, owner, field, expression, parent)) = work.pop() {
budget.lowering_entries(visited + 1, expression.span)?;
storage.release(1);
sources.at.set(Some(file));
if storage.0.is_none() {
budget.lowering_entries(visited + 1, expression.span)?;
}
visited += 1;
let site = *sites
.get(&(
@@ -566,7 +779,7 @@ impl<'s> Index<'s> {
"Parsed expression is absent from the source arena.",
)
})?;
if lowered.expressions[site].parent != parent {
if expressions[site].parent != parent {
return Err(error(
expression.span,
"Arena parent/child edge differs from original syntax.",
@@ -574,6 +787,7 @@ impl<'s> Index<'s> {
}
let mut child = |value: &'a super::Expression, edge| -> Result<(), Error> {
budget.lowering_entries(work.len() + 1, value.span)?;
storage.reserve(budget, 1, 0, value.span)?;
work.push((file, owner, field, value, Some((site, edge))));
Ok(())
};
@@ -623,7 +837,14 @@ impl<'s> Index<'s> {
_ => {}
}
}
if visited != lowered.expressions.len() {
sources.at.set(None);
let scratch = globals.len() + parameters.len() + sites.len();
drop(globals);
drop(parameters);
drop(sites);
drop(work);
storage.release(scratch);
if visited != expressions.len() {
return Err(error(
Span::default(),
"Arena contains expressions absent from parsed syntax.",
@@ -636,20 +857,24 @@ impl<'s> Index<'s> {
&mut self,
key: OccurrenceKey,
location: Location,
witnesses: &BTreeMap<usize, Vec<lex::Token>>,
sources: &Original<'_>,
physical: &mut BTreeSet<(usize, usize, usize)>,
budget: &Budget,
storage: &mut Storage<'_>,
) -> Result<(), Error> {
budget.lowering_occurrences(self.occurrences.len() + 1, location.span)?;
let text = checked_source(self.sources, location)?;
if storage.0.is_none() {
budget.lowering_occurrences(self.occurrences.len() + 1, location.span)?;
}
storage.reserve(budget, 2, 0, location.span)?;
let text = sources.checked(location)?;
if self.names.get(&key.target).is_none_or(|name| name != text) {
return Err(error(
location.span,
"Token differs from its qualified target name.",
));
}
let tokens = witnesses
.get(&location.file)
let tokens = sources
.tokens(location.file)
.ok_or_else(|| error(location.span, "Missing lexical witness."))?;
let token = tokens
.binary_search_by_key(&location.span.start, |token| token.span.start)
@@ -1,5 +1,5 @@
//! Private complete-Project admission from the actual complete syntax witness.
//! No Index, update authority, public source8 dispatch or replacement policy.
//! Consumed read-only typed index; no update authority, public source8 dispatch or replacement policy.
use super::super::{
Binding, Budget, Error as TypedError, Expression, ExpressionKind, Reference, Span,
declarations as d, error, lower,
@@ -9,8 +9,9 @@ use crate::language::{Ast, DeclKind};
use crate::source_project::{SourceDiagnostic, SourceLocation};
use crate::{Id, Kind, Project, RelationKind, analysis, language, validation};
use std::collections::BTreeMap;
mod accounting;
pub(in crate::language::analysis_source) mod accounting;
use accounting::{Ledger, Slots, add};
pub(in crate::language::analysis_source) mod ordinary_index;
/// Read-only authority: every component originates in the consumed syntax.
pub(in crate::language::analysis_source) struct ResolvedMixedProject<'s> {
@@ -32,11 +33,111 @@ 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.
pub(in crate::language::analysis_source) struct IndexedMixedProject<'s> {
resolved: ResolvedMixedProject<'s>,
data: super::super::index::IndexData,
}
impl<'s> ResolvedMixedProject<'s> {
pub fn index(mut self) -> Result<IndexedMixedProject<'s>, Failure> {
let input = IndexInput {
files: &self.syntax.files,
project: &self.project,
expressions: &self.expressions,
occurrences: &self.occurrences,
};
let data =
super::super::index::mixed_data(input, &mut self.syntax.budget, &mut self.storage)
.map_err(|failure| Failure::index(&self.syntax, failure))?;
Ok(IndexedMixedProject {
resolved: self,
data,
})
}
}
impl IndexedMixedProject<'_> {
pub fn project(&self) -> &Project {
self.resolved.project()
}
pub fn warnings(&self) -> &[SourceDiagnostic] {
self.resolved.warnings()
}
pub fn bundle(&self) -> &crate::source_project::SourceBundle {
self.resolved.syntax.bundle
}
/// Canonical file key, canonical path, and the complete original bytes.
pub fn files(&self) -> impl Iterator<Item = (usize, &str, &str)> {
self.resolved
.syntax
.files
.iter()
.enumerate()
.map(|(key, f)| (key, f.path, f.source))
}
pub fn names(&self) -> &BTreeMap<lower::Target, String> {
&self.data.names
}
pub fn occurrences(
&self,
) -> &BTreeMap<super::super::index::OccurrenceKey, super::super::index::Location> {
&self.data.occurrences
}
pub fn expressions(
&self,
) -> &BTreeMap<super::super::index::ExpressionKey, super::super::index::Location> {
&self.data.expressions
}
}
/// Constructor-only borrowed adapter. Its private file table prevents fabrication;
/// no API returns it and no alternative source/token table can be installed.
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],
}
impl IndexInput<'_> {
pub(in crate::language::analysis_source) fn declarations(
&self,
) -> impl Iterator<Item = (usize, &d::Declaration)> + Clone {
self.files
.iter()
.enumerate()
.flat_map(|(file, f)| f.typed.iter().map(move |d| (file, d)))
}
pub(in crate::language::analysis_source) fn source(&self, file: usize) -> Option<&str> {
self.files.get(file).map(|f| f.source)
}
pub(in crate::language::analysis_source) fn tokens(
&self,
file: usize,
) -> Option<&[super::super::lex::Token]> {
self.files.get(file).map(|f| f.tokens.as_slice())
}
}
#[derive(Debug)]
pub(in crate::language::analysis_source) struct Failure {
pub diagnostics: Vec<SourceDiagnostic>,
}
impl Failure {
/// Canonical attribution when justified; otherwise a safe explicit start fallback.
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| {
syntax.files[file]
.source
.get(failure.error.span.start..failure.error.span.end)
.is_none()
}) {
failure.error.span = Span::default();
}
Self::typed(syntax, failure.file, failure.error)
}
// Rejection transport is not retained in a successful witness. In particular
// a storage refusal needs a small diagnostic even when its allowance is zero.
fn typed(syntax: &ParsedMixedSyntax<'_>, file: Option<usize>, e: TypedError) -> Self {
@@ -3,7 +3,7 @@ use super::*;
use crate::language::{EditKind, EditValue};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(super) struct Slots {
pub(in crate::language::analysis_source) struct Slots {
pub entries: usize,
pub occurrences: usize,
pub text: usize,
@@ -39,7 +39,7 @@ pub(super) fn mul(a: usize, b: usize, at: Span) -> Result<usize, super::super::s
}
#[derive(Debug, Default)]
pub(super) struct Ledger {
pub(in crate::language::analysis_source) struct Ledger {
pub live: Slots,
}
impl Ledger {
@@ -0,0 +1,431 @@
//! Complete authored-name ownership from the sealed ordinary position projection.
//! Physical keys belong only to this immutable snapshot. This is not edit authority.
use super::super::super::{index, lex};
use super::*;
use crate::language::{EditField, EditKind, EditValue};
use std::collections::BTreeSet;
/// Canonical file key and the whole alias Word's half-open byte range.
pub(in crate::language::analysis_source) type OrdinaryKey = (usize, usize, usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::language::analysis_source) enum OrdinaryRole {
Header,
Reference,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::language::analysis_source) struct OrdinaryOccurrence {
pub owner: Id,
pub target: Id,
pub role: OrdinaryRole,
/// Alias token for Header; marker, intervening trivia and alias for Reference.
pub extent: Span,
}
/// Owns both occurrence families and their complete original admission witness.
pub(in crate::language::analysis_source) struct AuthoredMixedProject<'s> {
indexed: IndexedMixedProject<'s>,
ordinary: BTreeMap<OrdinaryKey, OrdinaryOccurrence>,
}
impl<'s> IndexedMixedProject<'s> {
pub fn index_ordinary(mut self) -> Result<AuthoredMixedProject<'s>, Failure> {
let ordinary = build(
&self.resolved.syntax,
&self.data,
&mut self.resolved.storage,
)
.map_err(|failure| Failure::index(&self.resolved.syntax, failure))?;
Ok(AuthoredMixedProject {
indexed: self,
ordinary,
})
}
}
impl AuthoredMixedProject<'_> {
pub fn project(&self) -> &Project {
self.indexed.project()
}
pub fn warnings(&self) -> &[SourceDiagnostic] {
self.indexed.warnings()
}
pub fn bundle(&self) -> &crate::source_project::SourceBundle {
self.indexed.bundle()
}
pub fn files(&self) -> impl Iterator<Item = (usize, &str, &str)> {
self.indexed.files()
}
pub fn names(&self) -> &BTreeMap<lower::Target, String> {
self.indexed.names()
}
pub fn typed_occurrences(&self) -> &BTreeMap<index::OccurrenceKey, index::Location> {
self.indexed.occurrences()
}
pub fn expressions(&self) -> &BTreeMap<index::ExpressionKey, index::Location> {
self.indexed.expressions()
}
pub fn ordinary_occurrences(&self) -> &BTreeMap<OrdinaryKey, OrdinaryOccurrence> {
&self.ordinary
}
}
struct Builder<'a, 'b> {
budget: &'a Budget,
storage: &'b mut Ledger,
globals: BTreeMap<&'a str, (Id, bool)>,
typed: BTreeSet<OrdinaryKey>,
ordinary: BTreeMap<OrdinaryKey, OrdinaryOccurrence>,
}
fn build(
syntax: &ParsedMixedSyntax<'_>,
data: &index::IndexData,
storage: &mut Ledger,
) -> Result<BTreeMap<OrdinaryKey, OrdinaryOccurrence>, lower::Failure> {
let mut file = None;
let mut b = Builder {
budget: &syntax.budget,
storage,
globals: BTreeMap::new(),
typed: BTreeSet::new(),
ordinary: BTreeMap::new(),
};
let result = (|| {
b.budget.active()?;
for (target, name) in &data.names {
if let lower::Target::Global(id) = target {
b.reserve(Span::default())?;
if b.globals.insert(name, (*id, false)).is_some() {
return Err(error(
Span::default(),
"Duplicate ordinary global spelling.",
));
}
}
}
for (key, at) in &data.occurrences {
file = Some(at.file);
let unit = syntax
.files
.get(at.file)
.ok_or_else(|| error(at.span, "Ordinary index has no canonical file."))?;
source(unit, at.span)?;
b.reserve(at.span)?;
if !b.typed.insert((at.file, at.span.start, at.span.end)) {
return Err(error(at.span, "Duplicate typed physical name token."));
}
if key.role == index::TokenRole::Declaration {
if key.target != lower::Target::Global(key.owner) {
return Err(error(at.span, "Typed global header has a different owner."));
}
b.header(key.owner, source(unit, at.span)?, at.span)?;
}
}
for (number, unit) in syntax.files.iter().enumerate() {
file = Some(number);
if let Some(header) = &unit.ordinary.header {
if unit.path != syntax.bundle.entry {
return Err(error(
header.span,
"Project header is outside the canonical entry.",
));
}
source(unit, header.span)?;
let start = token_at(unit, header.span.start, header.span)?;
let keyword = unit.tokens[start];
if keyword.class != lex::Class::Word || source(unit, keyword.span)? != "project" {
return Err(error(
header.span,
"Project header lacks its original project token.",
));
}
let alias = *unit
.tokens
.get(start + 1)
.ok_or_else(|| error(header.span, "Project header lacks an alias token."))?;
contains(header.span, alias.span)?;
word(unit, alias.span, &header.project.alias)?;
b.header(header.project.id, &header.project.alias, alias.span)?;
b.insert(
number,
alias.span,
OrdinaryOccurrence {
owner: header.project.id,
target: header.project.id,
role: OrdinaryRole::Header,
extent: alias.span,
},
)?;
}
if unit.ordinary.declarations.len() != unit.positions.len() {
return Err(error(
Span::default(),
"Ordinary declaration position count differs from syntax.",
));
}
for (declaration, (owner, position)) in
unit.ordinary.declarations.iter().zip(&unit.positions)
{
if *owner != declaration.id
|| position.span != declaration.span
|| position.body != declaration.body
|| position.lexical_owner != declaration.lexical_owner
{
return Err(error(
position.span,
"Ordinary position differs from its declaration owner.",
));
}
source(unit, declaration.span)?;
source(unit, declaration.body)?;
contains(declaration.span, declaration.body)?;
let alias = *position.header.get("alias").ok_or_else(|| {
error(
position.span,
"Ordinary declaration lacks an alias position.",
)
})?;
let start = token_at(unit, declaration.span.start, declaration.span)?;
if unit.tokens.get(start + 1).map(|t| t.span) != Some(alias)
|| alias.end > declaration.body.start
{
return Err(error(
alias,
"Ordinary alias is not its declaration header token.",
));
}
contains(declaration.span, alias)?;
word(unit, alias, &declaration.alias)?;
b.header(*owner, &declaration.alias, alias)?;
b.insert(
number,
alias,
OrdinaryOccurrence {
owner: *owner,
target: *owner,
role: OrdinaryRole::Header,
extent: alias,
},
)?;
// The synthetic root includes braces; actual fields lie in the interior.
let root_start = declaration.body.start.checked_sub(1).ok_or_else(|| {
error(position.fields.span, "Invalid ordinary body boundary.")
})?;
let root_end = declaration.body.end.checked_add(1).ok_or_else(|| {
error(position.fields.span, "Invalid ordinary body boundary.")
})?;
if position.fields.span
!= (Span {
start: root_start,
end: root_end,
})
{
return Err(error(
position.fields.span,
"Ordinary synthetic root differs from its body braces.",
));
}
source(unit, position.fields.span)?;
contains(declaration.span, position.fields.span)?;
let EditKind::Record(fields) = &position.fields.kind else {
return Err(error(
position.fields.span,
"Ordinary synthetic root must be a record.",
));
};
b.fields(
number,
unit,
*owner,
declaration.span,
declaration.body,
fields,
)?;
}
}
file = None;
if b.globals.values().any(|(_, seen)| !seen) {
return Err(error(
Span::default(),
"Global name is missing its authored declaration header.",
));
}
Ok(())
})();
result.map_err(|error| lower::Failure { file, error })?;
let globals = b.globals.len();
let typed = b.typed.len();
drop(b.globals);
drop(b.typed);
b.storage.release(Slots::entries(
add(globals, typed, Span::default())
.map_err(|error| lower::Failure { file: None, error })?,
));
Ok(b.ordinary)
}
impl Builder<'_, '_> {
fn reserve(&mut self, at: Span) -> Result<(), TypedError> {
self.storage.reserve(self.budget, Slots::entries(1), at)
}
fn header(&mut self, id: Id, alias: &str, at: Span) -> Result<(), TypedError> {
let Some((target, seen)) = self.globals.get_mut(alias) else {
return Err(error(at, "Ordinary header has no global target."));
};
if *target != id {
return Err(error(
at,
"Ordinary header differs from its global identity.",
));
}
if *seen {
return Err(error(
at,
"Global declaration header appears more than once.",
));
}
*seen = true;
Ok(())
}
fn insert(
&mut self,
file: usize,
alias: Span,
occurrence: OrdinaryOccurrence,
) -> Result<(), TypedError> {
let key = (file, alias.start, alias.end);
if self.typed.contains(&key) || self.ordinary.contains_key(&key) {
return Err(error(
alias,
"Duplicate ordinary or typed physical name token.",
));
}
self.reserve(alias)?;
self.ordinary.insert(key, occurrence);
Ok(())
}
fn fields(
&mut self,
file: usize,
unit: &FileSyntax<'_>,
owner: Id,
declaration: Span,
body: Span,
fields: &BTreeMap<String, EditField>,
) -> Result<(), TypedError> {
let mut work = Vec::new();
for field in fields.values() {
self.push_field(unit, &mut work, field, body)?;
}
while let Some((value, parent)) = work.pop() {
self.storage.release(Slots::entries(1));
source(unit, value.span)?;
contains(parent, value.span)?;
contains(declaration, value.span)?;
match &value.kind {
EditKind::Reference(alias) => {
let start = token_at(unit, value.span.start, value.span)?;
let marker = unit.tokens[start];
if marker.class != lex::Class::Punctuation || source(unit, marker.span)? != "@"
{
return Err(error(
value.span,
"Ordinary reference lacks its original marker token.",
));
}
let name = *unit.tokens.get(start + 1).ok_or_else(|| {
error(value.span, "Ordinary reference lacks its alias token.")
})?;
if name.span.end != value.span.end {
return Err(error(
value.span,
"Ordinary reference extent differs from its complete alias token.",
));
}
word(unit, name.span, alias)?;
let target = self
.globals
.get(alias.as_str())
.ok_or_else(|| {
error(name.span, "Ordinary reference has no global target.")
})?
.0;
self.insert(
file,
name.span,
OrdinaryOccurrence {
owner,
target,
role: OrdinaryRole::Reference,
extent: value.span,
},
)?;
}
EditKind::Record(fields) => {
for field in fields.values() {
self.push_field(unit, &mut work, field, value.span)?;
}
}
EditKind::List(items) => {
for item in items {
self.reserve(item.span)?;
work.push((item, value.span));
}
}
EditKind::Atom | EditKind::HistoricalUuid(_) => {}
}
}
drop(work);
Ok(())
}
fn push_field<'a>(
&mut self,
unit: &FileSyntax<'_>,
work: &mut Vec<(&'a EditValue, Span)>,
field: &'a EditField,
parent: Span,
) -> Result<(), TypedError> {
source(unit, field.span)?;
contains(parent, field.span)?;
contains(field.span, field.value.span)?;
self.reserve(field.value.span)?;
work.push((&field.value, field.span));
Ok(())
}
}
fn source<'a>(unit: &'a FileSyntax<'_>, span: Span) -> Result<&'a str, TypedError> {
unit.source
.get(span.start..span.end)
.ok_or_else(|| error(span, "Ordinary span is outside its original UTF-8 file."))
}
fn contains(parent: Span, child: Span) -> Result<(), TypedError> {
if child.start < parent.start || child.end > parent.end || child.start > child.end {
return Err(error(child, "Ordinary span crosses its containing syntax."));
}
Ok(())
}
fn token_at(unit: &FileSyntax<'_>, start: usize, at: Span) -> Result<usize, TypedError> {
unit.tokens
.binary_search_by_key(&start, |t| t.span.start)
.map_err(|_| {
error(
at,
"Ordinary occurrence does not start at an original token.",
)
})
}
fn word(unit: &FileSyntax<'_>, at: Span, expected: &str) -> Result<(), TypedError> {
let token = unit.tokens[token_at(unit, at.start, at)?];
if token.class != lex::Class::Word || token.span != at {
return Err(error(
at,
"Ordinary alias is not an original whole Word token.",
));
}
if source(unit, at)? != expected {
return Err(error(
at,
"Ordinary alias differs from its retained spelling.",
));
}
Ok(())
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,694 @@
use super::super::tests::{accepted, id, rich_bundle, rich_model, single};
use super::*;
use crate::Layer;
use crate::source_project::{ProjectLimits, SourceBundle};
fn indexed(bundle: &SourceBundle) -> IndexedMixedProject<'_> {
accepted(bundle).index().unwrap()
}
fn span(start: usize, end: usize) -> Span {
Span { start, end }
}
fn record(
owner: usize,
target: usize,
role: OrdinaryRole,
start: usize,
end: usize,
) -> OrdinaryOccurrence {
OrdinaryOccurrence {
owner: id(owner),
target: id(target),
role,
extent: span(start, end),
}
}
fn fail(indexed: IndexedMixedProject<'_>, message: &str, path: &str) -> SourceDiagnostic {
let failure = match indexed.index_ordinary() {
Ok(_) => panic!("damaged ordinary index accepted"),
Err(e) => e,
};
assert_eq!(failure.diagnostics.len(), 1);
let diagnostic = failure.diagnostics.into_iter().next().unwrap();
assert_eq!(diagnostic.path, path);
assert_eq!(diagnostic.diagnostic.message, message);
diagnostic
}
#[test]
fn entire_rich_oa_sa_and_unicode_crlf_maps_preserve_complete_authority() {
use OrdinaryRole::{Header as H, Reference as R};
for layer in [Layer::Oa, Layer::Sa] {
for crlf in [false, true] {
let mut bundle = rich_bundle(layer);
if crlf {
for source in bundle.files.values_mut() {
*source = format!("// 雨\r\n{}", source.replace('\n', "\r\n"));
}
}
let indexed = indexed(&bundle);
let names = indexed.names().clone();
let typed = indexed.occurrences().clone();
let expressions = indexed.expressions().clone();
let warnings = indexed.warnings().to_vec();
let limits = indexed.resolved.static_limits;
let static_usage = indexed.resolved.static_usage;
let usage = indexed.resolved.syntax.budget.usage;
let numeric = indexed.resolved.syntax.budget.numeric_units();
let before = indexed.resolved.storage.live;
let complete = indexed.index_ordinary().unwrap();
let endpoints = if crlf {
[28, 133, 188, 200, 363, 426, 505]
} else {
[19, 122, 177, 189, 350, 412, 490]
};
let mut expected = BTreeMap::new();
for (at, owner, target, role) in [
(endpoints[0], 1, 1, H),
(endpoints[1], 4, 4, H),
(endpoints[2], 4, 5, R),
(endpoints[3], 4, 3, R),
(endpoints[4], 2, 2, H),
(endpoints[5], 5, 5, H),
] {
expected.insert(
(2, at, at + 1),
record(
owner,
target,
role,
if role == R { at - 1 } else { at },
at + 1,
),
);
}
if layer == Layer::Sa {
let at = endpoints[6];
expected.insert((2, at, at + 1), record(6, 6, H, at, at + 1));
}
assert_eq!(complete.ordinary_occurrences(), &expected);
assert_eq!(complete.project(), &rich_model(layer));
assert!(std::ptr::eq(complete.bundle(), &bundle));
assert_eq!(
complete.files().collect::<Vec<_>>(),
bundle
.files
.iter()
.enumerate()
.map(|(i, (p, s))| (i, p.as_str(), s.as_str()))
.collect::<Vec<_>>()
);
assert_eq!(complete.names(), &names);
assert_eq!(complete.typed_occurrences(), &typed);
assert_eq!(complete.expressions(), &expressions);
assert_eq!(complete.warnings(), &warnings);
assert_eq!(complete.indexed.resolved.static_limits, limits);
assert_eq!(complete.indexed.resolved.static_usage, static_usage);
assert_eq!(complete.indexed.resolved.syntax.budget.usage, usage);
assert_eq!(
complete.indexed.resolved.syntax.budget.numeric_units(),
numeric
);
assert_eq!(
complete.indexed.resolved.storage.live,
before
.add(Slots::entries(expected.len()), Span::default())
.unwrap()
);
let mut headers: BTreeSet<_> = complete
.ordinary
.values()
.filter(|o| o.role == H)
.map(|o| o.target)
.collect();
headers.extend(
complete
.typed_occurrences()
.keys()
.filter(|k| k.role == index::TokenRole::Declaration)
.map(|k| k.owner),
);
assert_eq!(
headers,
complete
.names()
.keys()
.filter_map(|t| if let lower::Target::Global(id) = t {
Some(*id)
} else {
None
})
.collect()
);
}
}
}
#[test]
fn canonical_file_keys_distinguish_equal_header_offsets_and_exclude_include_prose() {
let mut bundle = single("include \"b.arcadia\"; include \"a.arcadia\";");
for (path, alias, n) in [("a.arcadia", "aa", 2), ("b.arcadia", "bb", 3)] {
bundle.files.insert(
path.into(),
format!(
"oa_activity {alias} \"Same @aa @bb\" id \"{}\" {{ {} }}",
id(n),
if n == 3 { "parent @aa;" } else { "" }
),
);
}
let complete = indexed(&bundle).index_ordinary().unwrap();
assert_eq!(
complete.ordinary,
BTreeMap::from([
((0, 12, 14), record(2, 2, OrdinaryRole::Header, 12, 14)),
((1, 12, 14), record(3, 3, OrdinaryRole::Header, 12, 14)),
((1, 82, 84), record(3, 2, OrdinaryRole::Reference, 81, 84)),
((2, 19, 20), record(1, 1, OrdinaryRole::Header, 19, 20)),
])
);
}
fn rich_with_extra_headers() -> SourceBundle {
let mut bundle = rich_bundle(Layer::Oa);
let source = bundle.files.get_mut("main.arcadia").unwrap();
let at = source.rfind('}').unwrap();
let extra = (20..26)
.map(|n| format!("oa_activity extra{n} \"Extra\" id \"{}\" {{}}\n", id(n)))
.collect::<String>();
source.insert_str(at, &extra);
bundle
}
#[test]
fn exact_ordinary_peak_after_typed_peak_preserves_the_inherited_ledger() {
let bundle = rich_with_extra_headers();
let baseline = indexed(&bundle).resolved.storage.live;
// G13 + T21 + N12 =46, final ordinary header. Six extra leaf activities
// add six retained warnings and eighteen lower-catalog slots. The prior
// passing resolver cap97 therefore needs at most109; this stage needs111.
for (extra, fits) in [(45, false), (46, true)] {
let profile = super::super::super::super::budget::Limits {
entries: baseline.entries + extra,
..super::super::super::super::budget::Limits::source8(language::ParseLimits::UNLIMITED)
};
let parsed =
super::super::super::parse_with_profile(&bundle, ProjectLimits::default(), profile)
.unwrap();
let indexed = resolve(parsed).unwrap().index().unwrap();
assert_eq!(indexed.resolved.storage.live, baseline);
if fits {
assert_eq!(
indexed
.index_ordinary()
.unwrap()
.indexed
.resolved
.storage
.live,
baseline.add(Slots::entries(12), Span::default()).unwrap()
);
} else {
let diagnostic = fail(
indexed,
"Source8 lowering entry limit exceeded.",
"main.arcadia",
);
let at = bundle.files["main.arcadia"].find("extra25").unwrap();
assert_eq!(diagnostic.diagnostic.span, span(at, at + 7));
}
}
}
fn node_field<'a>(
indexed: &'a mut IndexedMixedProject<'_>,
name: &str,
) -> &'a mut language::EditField {
let EditKind::Record(fields) = &mut indexed.resolved.syntax.files[2].positions[0].1.fields.kind
else {
panic!("node fields")
};
fields.get_mut(name).unwrap()
}
#[test]
fn projection_corruptions_reach_owner_header_marker_extent_and_spelling_checks() {
let bundle = rich_bundle(Layer::Oa);
for case in 0..12 {
let mut indexed = indexed(&bundle);
let message = match case {
0 => {
indexed.resolved.syntax.files[2].positions[0].0 = id(3);
"Ordinary position differs from its declaration owner."
}
1 => {
indexed.resolved.syntax.files[2].positions[0]
.1
.lexical_owner = Some(id(2));
"Ordinary position differs from its declaration owner."
}
2 => {
indexed.resolved.syntax.files[2].positions[0]
.1
.header
.insert("alias", span(177, 178));
"Ordinary alias is not its declaration header token."
}
3 => {
indexed.resolved.syntax.files[2]
.ordinary
.header
.as_mut()
.unwrap()
.project
.id = id(2);
"Ordinary header differs from its global identity."
}
4 => {
indexed.resolved.syntax.files[2].positions[0]
.1
.fields
.span
.start += 1;
"Ordinary synthetic root differs from its body braces."
}
5 => {
node_field(&mut indexed, "diagram").value.span.start = 177;
"Ordinary reference lacks its original marker token."
}
6 => {
node_field(&mut indexed, "diagram").value.span.end = 177;
"Ordinary reference extent differs from its complete alias token."
}
7 => {
node_field(&mut indexed, "diagram").value.span.end = 179;
"Ordinary reference extent differs from its complete alias token."
}
8 => {
node_field(&mut indexed, "diagram").value.kind =
EditKind::Reference("other".into());
"Ordinary alias differs from its retained spelling."
}
9 => {
let token = indexed.resolved.syntax.files[2]
.tokens
.iter_mut()
.find(|t| t.span.start == 177)
.unwrap();
token.class = lex::Class::String;
"Ordinary alias is not an original whole Word token."
}
10 => {
node_field(&mut indexed, "diagram").span.start = 0;
"Ordinary span crosses its containing syntax."
}
_ => {
indexed.data.names.remove(&lower::Target::Global(id(5)));
"Ordinary reference has no global target."
}
};
fail(indexed, message, "main.arcadia");
}
}
#[test]
fn missing_header_and_duplicate_physical_ownership_are_rejected() {
let bundle = rich_bundle(Layer::Oa);
let mut missing = indexed(&bundle);
missing.resolved.syntax.files[2]
.ordinary
.declarations
.remove(1);
missing.resolved.syntax.files[2].positions.remove(1);
let diagnostic = fail(
missing,
"Global name is missing its authored declaration header.",
"main.arcadia",
);
assert_eq!(diagnostic.diagnostic.span, Span::default());
let mut duplicate = indexed(&bundle);
// A second field position points at the same full reference. Parser emission
// cannot produce this; the control isolates ordinary physical ownership.
let field = node_field(&mut duplicate, "element");
field.span = span(168, 179);
field.value.span = span(176, 178);
field.value.kind = EditKind::Reference("d".into());
fail(
duplicate,
"Duplicate ordinary or typed physical name token.",
"main.arcadia",
);
let mut cross = indexed(&bundle);
let key = cross
.data
.occurrences
.keys()
.find(|k| k.role == index::TokenRole::Use(lower::Role::ParameterUse))
.unwrap()
.clone();
cross.data.occurrences.insert(
key,
index::Location {
file: 2,
span: span(122, 123),
},
);
fail(
cross,
"Duplicate ordinary or typed physical name token.",
"main.arcadia",
);
}
#[test]
fn invalid_file_and_utf8_damage_use_safe_explicit_diagnostic_locations() {
let mut bundle = rich_bundle(Layer::Oa);
*bundle.files.get_mut("main.arcadia").unwrap() =
bundle.files["main.arcadia"].replace("node n", "// 雨\nnode n");
let mut bad = indexed(&bundle);
let at = bad.data.occurrences.values_mut().next().unwrap();
at.file = usize::MAX;
assert_eq!(
fail(bad, "Ordinary index has no canonical file.", "main.arcadia")
.diagnostic
.span,
Span::default()
);
let mut bad = indexed(&bundle);
let byte = bundle.files["main.arcadia"].find('雨').unwrap() + 1;
bad.resolved.syntax.files[2].positions[0].1.fields.span = span(byte, byte + 1);
assert_eq!(
fail(
bad,
"Ordinary synthetic root differs from its body braces.",
"main.arcadia"
)
.diagnostic
.span,
Span::default()
);
}
#[test]
fn ordinary_resolution_ignores_same_spelled_scoped_parameter_names() {
let mut bundle = rich_bundle(Layer::Oa);
let main = bundle.files.get_mut("main.arcadia").unwrap();
*main = main
.replace("oa_activity w", "oa_activity x")
.replace("subject @w", "subject @x");
let end = main.rfind('}').unwrap();
main.insert_str(
end,
&format!(
"artifact attach \"Attachment\" id \"{}\" {{ owner @x; uri \"x\"; }}\n",
id(20)
),
);
let complete = indexed(&bundle).index_ordinary().unwrap();
let ordinary = complete
.ordinary
.values()
.find(|r| r.owner == id(20) && r.role == OrdinaryRole::Reference)
.unwrap();
assert_eq!(ordinary.target, id(2));
assert!(complete.names().contains_key(&lower::Target::Parameter {
calculation: id(10),
parameter: analysis::ParameterId(id(1))
}));
}
#[test]
fn small_stack_success_and_late_header_failure_leave_no_partial_owner() {
std::thread::Builder::new()
.stack_size(128 * 1024)
.spawn(|| {
let bundle = rich_bundle(Layer::Oa);
drop(indexed(&bundle).index_ordinary().unwrap());
let mut bad = indexed(&bundle);
bad.data
.names
.insert(lower::Target::Global(id(999)), "unowned".into());
fail(
bad,
"Global name is missing its authored declaration header.",
"main.arcadia",
);
let expression = format!("{}1{}", "abs(".repeat(127), ")".repeat(127));
let deep = single(&format!(
"calculation c \"C\" id \"{}\" {{ result Number; body {expression}; }}",
id(2)
));
drop(indexed(&deep).index_ordinary().unwrap());
})
.unwrap()
.join()
.unwrap();
}
#[test]
fn duplicate_global_header_and_cross_file_projection_damage_are_distinct() {
let bundle = rich_bundle(Layer::Oa);
let mut duplicate = indexed(&bundle);
let mut key = duplicate
.data
.occurrences
.keys()
.find(|k| k.role == index::TokenRole::Declaration && k.owner == id(10))
.unwrap()
.clone();
key.field = Some(lower::Field::Body);
// Reclassify an existing whole call token as a second header; remove its
// former role so physical uniqueness cannot mask the header-coverage check.
let call = duplicate
.data
.occurrences
.iter()
.find(|(_, at)| at.file == 2 && at.span == span(291, 292))
.unwrap()
.0
.clone();
duplicate.data.occurrences.remove(&call);
duplicate.data.occurrences.insert(
key,
index::Location {
file: 2,
span: span(291, 292),
},
);
fail(
duplicate,
"Global declaration header appears more than once.",
"main.arcadia",
);
let mut bundle = single("include \"a.arcadia\"; include \"b.arcadia\";");
for (file, alias, n) in [("a.arcadia", "aa", 2), ("b.arcadia", "bb", 3)] {
bundle.files.insert(
file.into(),
format!("oa_activity {alias} \"Same\" id \"{}\" {{}}", id(n)),
);
}
let mut damaged = indexed(&bundle);
let other = damaged.resolved.syntax.files[1].positions.remove(0);
damaged.resolved.syntax.files[0].positions[0] = other;
fail(
damaged,
"Ordinary position differs from its declaration owner.",
"a.arcadia",
);
}
#[test]
fn nested_ordinary_declarations_use_no_typed_frame_policy() {
std::thread::Builder::new()
.stack_size(128 * 1024)
.spawn(|| {
let mut body = String::new();
for n in 2..34 {
body.push_str(&format!("oa_activity a{n} \"A\" id \"{}\" {{\n", id(n)));
}
body.push_str(&"}\n".repeat(32));
let bundle = single(&body);
let indexed = indexed(&bundle);
let usage = indexed.resolved.syntax.budget.usage;
let before = indexed.resolved.storage.live;
let complete = indexed.index_ordinary().unwrap();
assert_eq!(complete.ordinary.len(), 33);
assert!(
complete
.ordinary
.values()
.all(|o| o.role == OrdinaryRole::Header)
);
assert_eq!(complete.indexed.resolved.syntax.budget.usage, usage);
assert_eq!(
complete.indexed.resolved.storage.live,
before.add(Slots::entries(33), Span::default()).unwrap()
);
})
.unwrap()
.join()
.unwrap();
}
#[test]
fn malformed_reference_utf8_start_is_reported_without_slicing_panic() {
let mut bundle = rich_bundle(Layer::Oa);
*bundle.files.get_mut("main.arcadia").unwrap() =
bundle.files["main.arcadia"].replace("diagram @d", "diagram @ // 雨\r\n d");
let mut damaged = indexed(&bundle);
let byte = bundle.files["main.arcadia"].find('雨').unwrap() + 1;
node_field(&mut damaged, "diagram").value.span.start = byte;
let diagnostic = fail(
damaged,
"Ordinary span is outside its original UTF-8 file.",
"main.arcadia",
);
assert_eq!(diagnostic.diagnostic.span, Span::default());
}
const CATEGORIES: &str = include_str!("tests/fixtures/categories.arcadia");
fn categories() -> SourceBundle {
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([("main.arcadia".into(), CATEGORIES.into())]),
}
}
#[test]
fn every_authored_category_reference_excludes_trivia_prose_and_historical_uuid() {
use OrdinaryRole::{Header as H, Reference as R};
let bundle = categories();
assert_eq!(CATEGORIES.len(), 2152);
let indexed = indexed(&bundle);
let model = indexed.project().clone();
let warnings = indexed.warnings().to_vec();
let before = indexed.resolved.storage.live;
let usage = indexed.resolved.syntax.budget.usage;
let numeric = indexed.resolved.syntax.budget.numeric_units();
let complete = indexed.index_ordinary().unwrap();
// Entire independently authored36-row map, checked by literal UTF-8 arithmetic.
let rows = [
(19, 20, 1, 1, H, 19),
(168, 169, 2, 2, H, 168),
(249, 254, 3, 3, H, 249),
(344, 345, 3, 2, R, 323),
(369, 377, 4, 4, H, 369),
(487, 488, 5, 5, H, 487),
(561, 565, 6, 6, H, 561),
(625, 630, 6, 3, R, 624),
(640, 648, 6, 4, R, 639),
(664, 665, 7, 7, H, 664),
(741, 745, 7, 6, R, 740),
(789, 793, 7, 6, R, 788),
(818, 822, 8, 8, H, 818),
(885, 889, 8, 6, R, 884),
(925, 926, 9, 9, H, 925),
(992, 997, 9, 3, R, 991),
(1003, 1004, 9, 5, R, 1002),
(1328, 1329, 10, 10, H, 1328),
(1391, 1394, 10, 12, R, 1390),
(1397, 1402, 10, 14, R, 1396),
(1422, 1425, 11, 11, H, 1422),
(1487, 1491, 11, 13, R, 1486),
(1514, 1517, 12, 12, H, 1514),
(1577, 1578, 12, 10, R, 1576),
(1617, 1620, 12, 11, R, 1616),
(1648, 1652, 13, 13, H, 1648),
(1712, 1715, 13, 11, R, 1711),
(1741, 1746, 13, 3, R, 1740),
(1789, 1794, 14, 14, H, 1789),
(1855, 1856, 14, 10, R, 1854),
(1882, 1890, 14, 4, R, 1881),
(1935, 1942, 15, 15, H, 1935),
(2004, 2005, 15, 10, R, 2003),
(2029, 2032, 15, 12, R, 2028),
(2047, 2051, 15, 13, R, 2046),
(2092, 2097, 15, 14, R, 2091),
];
let expected = rows
.into_iter()
.map(|(start, end, owner, target, role, extent)| {
((0, start, end), record(owner, target, role, extent, end))
})
.collect::<BTreeMap<_, _>>();
assert_eq!(complete.ordinary_occurrences(), &expected);
assert_eq!(complete.project(), &model);
assert_eq!(complete.warnings(), warnings);
assert!(std::ptr::eq(complete.bundle(), &bundle));
assert!(complete.typed_occurrences().is_empty());
assert!(complete.expressions().is_empty());
assert_eq!(
complete.indexed.resolved.storage.live,
before.add(Slots::entries(36), Span::default()).unwrap()
);
assert_eq!(complete.indexed.resolved.syntax.budget.usage, usage);
assert_eq!(
complete.indexed.resolved.syntax.budget.numeric_units(),
numeric
);
assert_eq!(complete.project().elements[&id(3)].parent, Some(id(2)));
assert_eq!(complete.project().elements[&id(4)].parent, Some(id(2)));
assert!(
!complete
.ordinary
.values()
.any(|o| o.owner == id(4) && o.role == R),
"implicit parent has no authored token"
);
assert_eq!(&CATEGORIES[323..345], "@ // @noise child\r\n a");
let Some(semantic) = complete.project().traces[&id(9)]
.baseline
.as_ref()
.unwrap()
.semantic
.as_ref()
else {
panic!("historical semantic baseline")
};
assert!(
matches!(&semantic.facets[0],crate::semantic::FacetReview::Field(crate::semantic::FieldReview::Type(review)) if review.oa==crate::semantic::Observed::Value(crate::TypeRef::Named(id(3))))
);
}
#[test]
fn exact_scratch_peak_reserves_live_iterative_slots_before_growth() {
let mut bundle = categories();
let points = ["{ x 0; y 0; }"; 64].join(", ");
let source = bundle.files.get_mut("main.arcadia").unwrap();
*source = source.replace(
"routes [{ edge @link; }];",
&format!("routes [{{ edge @link; waypoints [{points}]; }}];"),
);
let baseline = indexed(&bundle).resolved.storage.live;
// Unexpanded category peak52 is below the earlier resolver catalog/Vec
// overlap. Sixty-four valid route points isolate this stage: N11 + G15,
// pending edge1 + point values64, then pop one and push x/y =>W66.
// Exactly92 slots are live; earlier catalog overlap is only74.
for (extra, fits) in [(91, false), (92, true)] {
let profile = super::super::super::super::budget::Limits {
entries: baseline.entries + extra,
..super::super::super::super::budget::Limits::source8(language::ParseLimits::UNLIMITED)
};
let parsed =
super::super::super::parse_with_profile(&bundle, ProjectLimits::default(), profile)
.unwrap();
let indexed = resolve(parsed).unwrap().index().unwrap();
assert_eq!(indexed.resolved.storage.live, baseline);
if fits {
assert_eq!(
indexed
.index_ordinary()
.unwrap()
.indexed
.resolved
.storage
.live,
baseline.add(Slots::entries(36), Span::default()).unwrap()
);
} else {
let diagnostic = fail(
indexed,
"Source8 lowering entry limit exceeded.",
"main.arcadia",
);
let start = bundle.files["main.arcadia"].rfind("y 0").unwrap() + 2;
assert_eq!(diagnostic.diagnostic.span, span(start, start + 1));
}
}
}
@@ -0,0 +1,2 @@
# This exact-byte source fixture deliberately retains its authored CRLF.
categories.arcadia -text whitespace=cr-at-eol
@@ -0,0 +1,30 @@
arcadia 8;
project p "Authored @p child" id "00000000-0000-0000-0000-000000000001" {
description "Do not index @child, @link or uuid in prose 雨";
oa_activity a "Parent child" id "00000000-0000-0000-0000-000000000002" {
oa_activity child "Explicit child" id "00000000-0000-0000-0000-000000000003" { parent @ // @noise child
a; }
oa_activity implicit "Implicit child" id "00000000-0000-0000-0000-000000000004" { description "child @a"; }
}
sa_function s "Selected" id "00000000-0000-0000-0000-000000000005" {}
interaction link "Link" id "00000000-0000-0000-0000-000000000006" { source @child; target @implicit; }
diagram d "View" id "00000000-0000-0000-0000-000000000007" {
routes [{ edge @link; }];
view_options { hidden_edges [@link]; };
}
artifact note "Evidence" id "00000000-0000-0000-0000-000000000008" { owner @link; uri "docs/@child.md"; }
trace t "Review" id "00000000-0000-0000-0000-000000000009" {
oa @child; sa @s; kind Manual;
baseline {
oa { name "child"; description "@child"; };
sa { name "s"; description "@s"; };
semantic { facets [{ Field { Type { oa { Value { Named uuid "00000000-0000-0000-0000-000000000003"; }; }; sa { Value Boolean; }; }; }; }]; };
};
}
oa_process q "Caller" id "00000000-0000-0000-0000-000000000010" { steps [@use, @right]; }
oa_process sub "Callee" id "00000000-0000-0000-0000-000000000011" { steps [@left]; }
oa_occurrence use "Call" id "00000000-0000-0000-0000-000000000012" { parent @q; body { SequenceReference { sequence @sub; }; }; }
oa_occurrence left "Left" id "00000000-0000-0000-0000-000000000013" { parent @sub; body { Action { action @child; lifeline none; }; }; }
oa_occurrence right "Right" id "00000000-0000-0000-0000-000000000014" { parent @q; body { Action { action @implicit; lifeline none; }; }; }
contextual_link context "Context" id "00000000-0000-0000-0000-000000000015" { owner @q; source { references [@use]; occurrence @left; }; target { references []; occurrence @right; }; purpose { Precedence { guard "@child"; }; }; }
}
@@ -6,7 +6,7 @@ use crate::analysis_numeric::Exact;
use crate::source_project::{ProjectLimits, SourceBundle};
use crate::*;
fn id(n: usize) -> Id {
pub(super) fn id(n: usize) -> Id {
Id::parse_str(&format!("00000000-0000-0000-0000-{n:012}")).unwrap()
}
fn doc(body: &str) -> String {
@@ -15,7 +15,7 @@ fn doc(body: &str) -> String {
id(1)
)
}
fn single(body: &str) -> SourceBundle {
pub(super) fn single(body: &str) -> SourceBundle {
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([("main.arcadia".into(), doc(body))]),
@@ -144,7 +144,7 @@ fn expected(layer: Layer) -> Project {
)]),
}
}
fn accepted(bundle: &SourceBundle) -> ResolvedMixedProject<'_> {
pub(super) fn accepted(bundle: &SourceBundle) -> ResolvedMixedProject<'_> {
resolve(parse(bundle, ProjectLimits::default()).unwrap()).unwrap_or_else(|e| panic!("{e:?}"))
}
fn rejected(bundle: &SourceBundle) -> Failure {
@@ -250,9 +250,8 @@ fn complete_oa_sa_forward_model_warning_and_original_authority() {
}
}
#[test]
fn complete_global_arena_scoped_calls_and_coincident_file_offsets() {
let mut bundle = bundle(Layer::Oa);
pub(super) fn rich_bundle(layer: Layer) -> SourceBundle {
let mut bundle = bundle(layer);
let main = bundle.files.get_mut("main.arcadia").unwrap();
*main = main
.replace(
@@ -269,8 +268,10 @@ fn complete_global_arena_scoped_calls_and_coincident_file_offsets() {
] {
bundle.files.insert(file.into(), format!("calculation {alias} \"{name}\" id \"{}\" {{\ninput x id \"{}\" : Number;\ninput y id \"{}\" : Number;\nresult Number; body $x {op} $y;\n}}\n", id(n), id(1), id(12)));
}
let resolved = accepted(&bundle);
let mut model = expected(Layer::Oa);
bundle
}
pub(super) fn rich_model(layer: Layer) -> Project {
let mut model = expected(layer);
for (alias, name, n, op) in [
("f", "F", 10, a::BinaryOp::Add),
("g", "G", 11, a::BinaryOp::Subtract),
@@ -315,6 +316,14 @@ fn complete_global_arena_scoped_calls_and_coincident_file_offsets() {
call(10, call(11, number(3), number(4)), number(2)),
call(10, number(5), number(6)),
));
model
}
#[test]
fn complete_global_arena_scoped_calls_and_coincident_file_offsets() {
let bundle = rich_bundle(Layer::Oa);
let resolved = accepted(&bundle);
let model = rich_model(Layer::Oa);
assert_eq!(resolved.project(), &model);
assert_eq!(
resolved
@@ -1021,3 +1030,5 @@ fn incomplete_call_borrows_large_signature_and_refuses_at_single_admission() {
assert_eq!(failure.diagnostics[0].diagnostic.code, "analysis_arguments");
assert_eq!(failure.diagnostics[0].diagnostic.object_id, Some(id(11)));
}
mod index_tests;
@@ -0,0 +1,483 @@
use super::*;
use crate::language::analysis_source::index::{Location, TokenRole};
use lower::{Child as C, Field as F, Role as R, Target as T};
fn parameter(n: usize) -> a::ParameterId {
a::ParameterId(id(n))
}
fn q(c: usize, p: usize) -> T {
T::Parameter {
calculation: id(c),
parameter: parameter(p),
}
}
fn arg(n: usize) -> C {
C::Argument(parameter(n))
}
fn mapped(original: &str, byte: usize, transformed: bool) -> usize {
if transformed {
8 + byte
+ original.as_bytes()[..byte]
.iter()
.filter(|&&b| b == b'\n')
.count()
} else {
byte
}
}
#[test]
fn entire_authored_index_oa_sa_original_and_unicode_crlf() {
for layer in [Layer::Oa, Layer::Sa] {
for transformed in [false, true] {
let original = rich_bundle(layer);
let mut bundle = original.clone();
if transformed {
for source in bundle.files.values_mut() {
*source = format!("// 雨\r\n{}", source.replace('\n', "\r\n"));
}
}
let resolved = accepted(&bundle);
let before = resolved.storage.live;
let usage = resolved.syntax.budget.usage;
let numeric = resolved.syntax.budget.numeric_units();
let warnings = resolved.warnings.clone();
let static_limits = resolved.static_limits;
let static_usage = resolved.static_usage;
let indexed = resolved.index().unwrap();
assert_eq!(indexed.project(), &rich_model(layer));
assert_eq!(indexed.warnings(), warnings);
assert!(std::ptr::eq(indexed.bundle(), &bundle));
// Root plus Left/Argument(1)/Argument(1) is four structural frames.
// Index verification may raise this peak without reparsing input.
let mut expected_usage = usage;
expected_usage.deepest_frame = expected_usage.deepest_frame.max(4);
assert_eq!(indexed.resolved.syntax.budget.usage, expected_usage);
assert_eq!(indexed.resolved.syntax.budget.numeric_units(), numeric);
assert_eq!(indexed.resolved.static_limits, static_limits);
assert_eq!(indexed.resolved.static_usage, static_usage);
assert_eq!(
indexed.files().collect::<Vec<_>>(),
bundle
.files
.iter()
.enumerate()
.map(|(i, (p, s))| (i, p.as_str(), s.as_str()))
.collect::<Vec<_>>()
);
let mut names: BTreeMap<_, String> = [
(1, "p"),
(2, "w"),
(3, "v"),
(4, "n"),
(5, "d"),
(10, "f"),
(11, "g"),
]
.into_iter()
.map(|(n, s)| (T::Global(id(n)), s.into()))
.collect();
for c in [10, 11] {
for (p, s) in [(1, "x"), (12, "y")] {
names.insert(q(c, p), s.into());
}
}
if layer == Layer::Sa {
names.insert(T::Global(id(6)), "s".into());
}
assert_eq!(indexed.names(), &names);
assert_eq!(
indexed.resolved.storage.live,
before
.add(
Slots {
entries: if layer == Layer::Sa { 60 } else { 59 },
occurrences: 0,
text: names.len()
},
Span::default()
)
.unwrap()
);
let at = |file: usize, start, end| {
let source = original.files.values().nth(file).unwrap();
Location {
file,
span: Span {
start: mapped(source, start, transformed),
end: mapped(source, end, transformed),
},
}
};
let mut expressions = BTreeMap::new();
for (file, owner) in [(0, 10), (1, 11)] {
for (path, start, end) in [
(vec![], 202, 209),
(vec![C::Left], 202, 204),
(vec![C::Right], 207, 209),
] {
expressions.insert((id(owner), F::Body, path), at(file, start, end));
}
}
for (path, start, end) in [
(vec![], 290, 334),
(vec![C::Left], 290, 317),
(vec![C::Left, arg(12)], 296, 297),
(vec![C::Left, arg(1)], 302, 316),
(vec![C::Left, arg(1), arg(1)], 308, 309),
(vec![C::Left, arg(1), arg(12)], 314, 315),
(vec![C::Right], 320, 334),
(vec![C::Right, arg(1)], 326, 327),
(vec![C::Right, arg(12)], 332, 333),
] {
expressions.insert((id(3), F::Base, path), at(2, start, end));
}
assert_eq!(
indexed
.expressions()
.iter()
.map(|(k, v)| ((k.owner, k.field, k.path.edges()), *v))
.collect::<BTreeMap<_, _>>(),
expressions
);
let mut occurrences = BTreeMap::new();
for (file, owner, start) in [(0, 10, 12), (1, 11, 12), (2, 3, 206)] {
occurrences.insert(
(
id(owner),
None,
None,
TokenRole::Declaration,
T::Global(id(owner)),
),
at(file, start, start + 1),
);
}
for (file, owner) in [(0, 10), (1, 11)] {
for (p, start) in [(1, 68), (12, 128)] {
occurrences.insert(
(
id(owner),
Some(F::Input(parameter(p))),
None,
TokenRole::Use(R::ParameterDeclaration),
q(owner, p),
),
at(file, start, start + 1),
);
}
for (p, edge, start) in [(1, C::Left, 203), (12, C::Right, 208)] {
occurrences.insert(
(
id(owner),
Some(F::Body),
Some(vec![edge]),
TokenRole::Use(R::ParameterUse),
q(owner, p),
),
at(file, start, start + 1),
);
}
}
occurrences.insert(
(
id(3),
Some(F::Subject),
None,
TokenRole::Use(R::Reference),
T::Global(id(2)),
),
at(2, 269, 270),
);
for (path, role, target, start) in [
(vec![C::Left], R::CallTarget, T::Global(id(10)), 291),
(vec![C::Left], R::ArgumentLabel, q(10, 12), 293),
(vec![C::Left], R::ArgumentLabel, q(10, 1), 299),
(vec![C::Left, arg(1)], R::CallTarget, T::Global(id(11)), 303),
(vec![C::Left, arg(1)], R::ArgumentLabel, q(11, 1), 305),
(vec![C::Left, arg(1)], R::ArgumentLabel, q(11, 12), 311),
(vec![C::Right], R::CallTarget, T::Global(id(10)), 321),
(vec![C::Right], R::ArgumentLabel, q(10, 1), 323),
(vec![C::Right], R::ArgumentLabel, q(10, 12), 329),
] {
occurrences.insert(
(
id(3),
Some(F::Base),
Some(path),
TokenRole::Use(role),
target,
),
at(2, start, start + 1),
);
}
assert_eq!(
indexed
.occurrences()
.iter()
.map(|(k, v)| (
(
k.owner,
k.field,
k.path.as_ref().map(|p| p.edges()),
k.role,
k.target
),
*v
))
.collect::<BTreeMap<_, _>>(),
occurrences
);
for (key, at) in indexed.occurrences() {
let source = indexed.files().nth(at.file).unwrap().2;
assert_eq!(&source[at.span.start..at.span.end], names[&key.target]);
}
}
}
}
fn reject(value: ResolvedMixedProject<'_>, message: &str, file: Option<&str>) -> Failure {
let failure = match value.index() {
Ok(_) => panic!("corrupt index accepted: {message}"),
Err(e) => e,
};
assert_eq!(failure.diagnostics[0].diagnostic.message, message);
if let Some(file) = file {
assert_eq!(failure.diagnostics[0].path, file);
}
failure
}
#[test]
fn damage_reaches_structural_token_and_call_checks() {
let bundle = rich_bundle(Layer::Oa);
for case in 0..13 {
let mut value = accepted(&bundle);
let message = match case {
0 => {
value.expressions[1].file = 1;
"Source span crosses its containing syntax."
}
1 => {
value.expressions[1].owner = id(1);
"Unknown expression owner."
}
2 => {
value.expressions[1].field = F::Predicate;
"Expression parent crosses its owner, field or file."
}
3 => {
value.expressions[1].parent = Some((1, C::Left));
"Expression parent must precede its child."
}
4 => {
value.expressions[1].parent = Some((2, C::Left));
"Expression parent must precede its child."
}
5 => {
value.expressions[1].parent = Some((0, C::Operand));
"Arena parent/child edge differs from original syntax."
}
6 => {
value.occurrences[11].target = q(11, 1);
"Argument label crosses its called calculation."
}
7 => {
value.occurrences.remove(11);
"Argument child has no qualified label."
}
8 => {
value.expressions.pop();
"Parsed expression is absent from the source arena."
}
9 => {
value.occurrences[1].span = value.occurrences[0].span;
value.occurrences[1].target = q(10, 1);
value.occurrences[1].field = F::Input(parameter(1));
"Duplicate qualified occurrence or token ownership."
}
10 => {
value.occurrences[0].span = Span { start: 69, end: 70 };
"Token differs from its qualified target name."
}
11 => {
let token = value.syntax.files[0]
.tokens
.iter_mut()
.find(|t| t.span.start == 68)
.unwrap();
token.class = super::super::super::super::lex::Class::String;
"Occurrence is not an original whole name token."
}
_ => {
value.expressions.push(lower::ExpressionSite {
file: 0,
owner: id(10),
field: F::Base,
parent: None,
span: Span {
start: 202,
end: 204,
},
});
"Arena contains expressions absent from parsed syntax."
}
};
let file = if matches!(case, 6..=8) {
Some("main.arcadia")
} else if case == 0 {
Some("b.arcadia")
} else if case == 12 {
None
} else {
Some("a.arcadia")
};
reject(value, message, file);
}
}
#[test]
fn new_maps_keep_baseline_and_scope_reservations_and_peak_scratch() {
let bundle = rich_bundle(Layer::Oa);
let baseline = accepted(&bundle).storage.live;
// Final new maps59; at the last occurrence, owners3+physical21+paths15
// coexist. Subsequent call peak77 and syntax peak90 are smaller than98.
for (extra, fits) in [(97, false), (98, true)] {
let profile = super::super::super::super::budget::Limits {
entries: baseline.entries + extra,
..super::super::super::super::budget::Limits::source8(language::ParseLimits::UNLIMITED)
};
let resolved =
resolve(parse_with_profile(&bundle, ProjectLimits::default(), profile).unwrap())
.unwrap();
assert_eq!(resolved.storage.live, baseline);
if fits {
let indexed = resolved.index().unwrap();
assert_eq!(indexed.resolved.storage.live.entries, baseline.entries + 59);
assert_eq!(
indexed.resolved.storage.live.occurrences,
baseline.occurrences
);
} else {
let failure = reject(
resolved,
"Source8 lowering entry limit exceeded.",
Some("main.arcadia"),
);
assert_eq!(
failure.diagnostics[0].diagnostic.span,
Span {
start: 329,
end: 330
}
);
}
}
}
#[test]
fn copied_name_bytes_coexist_with_retained_warning_bytes() {
let mut bundle = single("");
let source = bundle
.files
.remove("main.arcadia")
.unwrap()
.replace("\"Project\"", "\"\"");
bundle.entry = format!("{}.arcadia", "a".repeat(180));
bundle.files.insert(bundle.entry.clone(), source);
for (bytes, fits) in [(236, false), (237, true)] {
let profile = super::super::super::super::budget::Limits {
bytes,
..super::super::super::super::budget::Limits::source8(language::ParseLimits::UNLIMITED)
};
let resolved =
resolve(parse_with_profile(&bundle, ProjectLimits::default(), profile).unwrap())
.unwrap();
assert_eq!(resolved.storage.live.text, 236);
if fits {
assert_eq!(resolved.index().unwrap().resolved.storage.live.text, 237);
} else {
reject(resolved, "Source8 index/output byte limit exceeded.", None);
}
}
}
#[test]
fn success_and_late_failure_drop_on_small_stack() {
std::thread::Builder::new()
.stack_size(128 * 1024)
.spawn(|| {
let bundle = rich_bundle(Layer::Oa);
drop(accepted(&bundle).index().unwrap());
let deep = single(&format!(
"calculation c \"C\" id \"{}\" {{ result Number; body {}1{}; }}",
id(2),
"abs(".repeat(127),
")".repeat(127)
));
drop(accepted(&deep).index().unwrap());
let mut damaged = accepted(&deep);
damaged.expressions[127].parent = Some((126, C::Left));
reject(
damaged,
"Arena parent/child edge differs from original syntax.",
Some("main.arcadia"),
);
let mut value = accepted(&bundle);
value.expressions[1].parent = Some((0, C::Operand));
reject(
value,
"Arena parent/child edge differs from original syntax.",
Some("a.arcadia"),
);
})
.unwrap()
.join()
.unwrap();
}
#[test]
fn invalid_file_and_utf8_span_use_explicit_safe_fallback() {
let mut bundle = rich_bundle(Layer::Oa);
*bundle.files.get_mut("a.arcadia").unwrap() =
bundle.files["a.arcadia"].replace("\"F\"", "\"雨\"");
let mut value = accepted(&bundle);
value.expressions[0].file = usize::MAX;
let failure = reject(
value,
"Source span is outside its original UTF-8 file.",
Some("main.arcadia"),
);
assert_eq!(failure.diagnostics[0].diagnostic.span, Span::default());
let mut value = accepted(&bundle);
let byte = bundle.files["a.arcadia"].find('雨').unwrap() + 1;
value.occurrences[0].span = Span {
start: byte,
end: byte + 1,
};
let failure = reject(
value,
"Source span is outside its original UTF-8 file.",
Some("a.arcadia"),
);
assert_eq!(failure.diagnostics[0].diagnostic.span, Span::default());
}
#[test]
fn distinct_qualified_keys_cannot_share_one_physical_token() {
let bundle = rich_bundle(Layer::Oa);
let mut value = accepted(&bundle);
let original = &value.occurrences[2];
value.occurrences.push(lower::Occurrence {
file: original.file,
owner: original.owner,
field: original.field,
expression: original.expression,
role: R::ArgumentLabel,
target: q(11, 1),
span: original.span,
});
reject(
value,
"Duplicate qualified occurrence or token ownership.",
Some("a.arcadia"),
);
}
@@ -0,0 +1,63 @@
# Private mixed typed occurrence index
Source checkpoint on `codex/analysis-mixed-index-956atf-20260924`, based on
`aaec78fcc9b273d1485497126392afdd1fb2e54e`. This records implemented structure;
compilation and runtime acceptance have not yet occurred at this checkpoint.
The first Windows strict core all-target Clippy attempt at `a18b7966` returned
101 for the test's `6 | 7 | 8` pattern (`manual_range_patterns`); all 545 source
inputs remained unchanged. Its receipt/log are preserved. The successor uses
`6..=8` with the same cases and no production change. No test body ran in that
attempt. At `1d02206c`, strict lint passes and the actual listing has 216 names.
The seven new tests initially yield six passed and one failed: the full fixture
incorrectly required the old lexical frame peak (three) to remain unchanged.
The contract explicitly permits structural depth checks to raise that peak;
root plus Left/Argument(1)/Argument(1) has four frames. Independent review
confirmed this test defect. The successor expects that exact maximum while
still comparing every other usage field, numeric work and static metadata.
Both original failures remain recorded; current-source runtime gates are pending.
The accepted contract/review are published at
`7cfdd13d3a1472e63fd34b0080b2cfc4b921b23e:harness/analysis-mixed-index-contract-20260924.md`.
Independent exhaustive fixture data and its raw-byte checker are at
`ae6df4fa1f103ff9b14963731b2edd84e38738b1:harness/analysis-mixed-index-20260924/`.
`ResolvedMixedProject::index` consumes the sealed complete-Project witness.
`IndexedMixedProject` owns it and shared `IndexData`; read-only methods expose
the Project, warnings, original bundle/canonical files, names and typed maps.
It has no conversion to the legacy numeric-source Index or its updater. The
constructor-only adapter keeps its file table private, so shared verification
does not accept caller-fabricated mixed source/token ownership.
Shared verification preserves original token, owner, structural child-path,
qualified call-label and physical occurrence checks. Mixed catalog construction
streams complete Project globals and scoped parameters once. It does not
reparse, relower, duplicate model records or repeat admission. Ordinary globals
are names/targets here; ordinary source occurrences are not indexed by this view.
Complete non-expression occurrence emission remains the sealed lowerer's
invariant, checked by an independent exhaustive output oracle.
Mixed allocations reserve against the retained aggregate ledger before growth.
New map/vector/set/Path entries charge entry slots; retained raw lowerer
occurrences keep occurrence slots. New names charge copied UTF-8 bytes. Owner
and physical-token scratch drop after occurrence checks; path/call scratch drop
before syntax-edge traversal. Shared Path nodes remain retained while map keys
own them. Legacy typed-only construction retains its original local policy.
Precompile independent review found stale diagnostic file attribution in two
late verification loops and unsafe fallback for invalid file/UTF-8 spans. The
source now updates attribution for each checked record and maps invalid
locations explicitly to a safe start span, retaining a valid file when known.
Regression cases are authored; no passing runtime result is implied.
Seven new tests cover complete OA/SA names/maps/model and Unicode/CRLF variants,
qualified corruption cases, inherited-plus-new storage and copied-name byte
boundaries, original provenance/admission metadata, and small-stack success and
late rejection. Existing rich fixture/model construction is factored without
removing its original full arena test. Actual test inventory, strict core lint,
focused checks, full private-library dispositions and formatting still require
current-source execution. Prior private source8 failures are not suppressed.
The working agreement is synchronized to current main blob
`e76647f49913b314e1543c4464f6199533909bae`. This source remains separate from the
Windows prerelease. Public source8, ordinary occurrence composition, source
updates/output admission, migration, checked save/history, GUI and API parity
remain required subsequent work.
@@ -0,0 +1,19 @@
# Private authored ordinary-name index implementation
Before compilation, source checkpoint `50a5918f` exposed Git normalization of
the CRLF category fixture. A narrow fixture attribute now preserves its exact
2,152 bytes and authored span oracle. Test-only resource variants were also
strengthened to reach this new stage after prior phases fit; the initial source
was not built or runtime-tested. Production bytes are unchanged by these repairs.
Local implementation on the `93eb6117f95353d25d594d59780018f197c51672` private lineage, following the root accepted ordinary-index contract (2026-09-24). Runtime acceptance is pending the parent-owned compiler/test gates; this note records design and authored evaluators, not passing results.
`IndexedMixedProject::index_ordinary(self)` consumes the admitted typed owner and returns `ordinary_index::AuthoredMixedProject`. It retains that owner and one map keyed by canonical file and whole alias-token byte range. Inline records carry owner/global target, Header/Reference role and full original extent. Read-only methods expose both occurrence families, expression keys, names, complete Project, warnings and original bundle/files. No adapter to legacy Index/Plan/Composition or independently replaceable source/profile is added.
Construction borrows original significant tokens and the sealed ordinary AST/position pairs. Headers are checked against declaration occurrences, not enclosing ranges; the synthetic root Record includes body braces, while real field/value extents are checked within their justified parents. Every Reference leaf is visited, including nested records/lists and arbitrary ordinary fields. A Reference must start at the significant `@` and end exactly at the immediately following whole Word. HistoricalUuid, Atom and prose/include/identity strings are excluded. No reparse, lower, model copy, name copy, numeric work, admission or frame call occurs. Completeness trusts the sealed projection plus exhaustive authored oracles; per-record checks do not establish arbitrary coordinated AST/projection omission rejection.
The inherited ledger remains live. New retention is exactly N=1+D+A entry slots, zero occurrence slots, zero text bytes. Scratch is G borrowed global-name entries with inline header-seen flags, T typed physical tokens and W currently queued borrowed values. Reserve before every map/set/work growth; a pop releases one logical slot; globals and typed lookup release after their containers drop. Work drains per declaration; the synthetic root is not queued. Actual rich OA/SA stage peaks are incoming baseline+34/+36 (retained+6/+7), but earlier typed peaks prevent those profiles from isolating this later stage. Rich plus six empty activities uses a later-stage46/45 boundary (retained12), clearing the preceding lower-catalog overlap including retained warnings; unexpanded category fixture has stage peak52 with W5, but that can be below preceding resolver overlap. The scratch-cap test adds64 finite route waypoints to a separate in-memory variant and uses92/91 with W66 (retained36). These hand-counts still require runtime verification, including earlier-stage admission.
Authored tests cover complete OA/SA maps with literal numeric spans, independent Unicode/CRLF spans, exact rich Project/model metadata retention, canonical coincident offsets and cross-file references, global versus calculation parameter spelling, nested explicit/implicit parents, comment-separated markers, relation/context/route/view/artifact/trace categories, historical UUID exclusion, precise corruption diagnostics and safe fallback, retained/scratch caps without Budget replacement, and bounded-stack success/late failure. The category literal initially contained an invalid duplicate route; the independent oracle critic caught it before compilation and the duplicate was removed. The corrected fixture has36 independently enumerated records (15 headers, 21 references). No inherited compatibility test or known failure disposition is removed.
Focused runtime at `147b247` reported 9 passing and 4 failing tests (parent-owned gate). Three failures were pre-index refusals from incorrectly authored C-style block comments: the shared raw scanner only treats `//` through newline as Comment. Category trivia now uses a byte-neutral line-comment replacement, preserving all36 numeric records and the full marker/alias extents; the UTF-8 corruption fixture likewise first admits a real line comment before damaging its retained start. The fourth control accidentally duplicated an already indexed call token before it could test header coverage; it now removes that token's former call role before assigning the forged second Header role. Intended later diagnostics remain unchanged. These are test/fixture corrections; production is unchanged, and the 147b247 failures remain evidence. Retest pending.