Strengthen staged expression oracles and preserve source8 evidence

This commit is contained in:
2026-09-12 20:48:41 -07:00
parent e8a18a7cf9
commit 2c515cf3bd
7 changed files with 540 additions and 0 deletions
@@ -21,6 +21,93 @@ fn number(value: &Expression) -> String {
exact.canonical_literal() exact.canonical_literal()
} }
fn binary_children(value: Expression, expected: BinaryOp) -> (Expression, Expression) {
let Kind::Binary { op, left, right } = value.kind else {
panic!("expected Binary {expected:?}");
};
assert_eq!(op, expected);
(*left, *right)
}
#[test]
fn mixed_precedence_and_grouping_have_independent_expected_child_topology() {
let (one, product) = binary_children(expression("1 + 2 * 3"), BinaryOp::Add);
assert_eq!(number(&one), "1");
let (two, three) = binary_children(product, BinaryOp::Multiply);
assert_eq!(
(number(&two), number(&three)),
("2".to_owned(), "3".to_owned())
);
let (conjunction, last) =
binary_children(expression("not(false) and true or false"), BinaryOp::Or);
assert_eq!(last.kind, Kind::Boolean(false));
let (negated, middle) = binary_children(conjunction, BinaryOp::And);
assert_eq!(middle.kind, Kind::Boolean(true));
let Kind::Unary { op, operand } = negated.kind else {
panic!("expected Not");
};
assert_eq!(op, UnaryOp::Not);
assert_eq!(operand.kind, Kind::Boolean(false));
let (negative, two) = binary_children(expression("-1 * 2"), BinaryOp::Multiply);
assert_eq!(number(&two), "2");
let Kind::Unary { op, operand } = negative.kind else {
panic!("expected Negate");
};
assert_eq!(op, UnaryOp::Negate);
assert_eq!(number(&operand), "1");
let (quotient, three) = binary_children(expression("1 / 2 * 3"), BinaryOp::Multiply);
assert_eq!(number(&three), "3");
let (one, two) = binary_children(quotient, BinaryOp::Divide);
assert_eq!(
(number(&one), number(&two)),
("1".to_owned(), "2".to_owned())
);
let (one, grouped) = binary_children(expression("1 + (2 + 3)"), BinaryOp::Add);
assert_eq!(number(&one), "1");
assert!(grouped.grouped);
let (two, three) = binary_children(grouped, BinaryOp::Add);
assert_eq!(
(number(&two), number(&three)),
("2".to_owned(), "3".to_owned())
);
}
#[test]
fn flat_binary_depth_and_malformed_constructor_retain_bounded_completed_children() {
let flat = |count: usize| vec!["true"; count].join(" and ");
let deep = flat(128);
let mut tree = expression(&deep);
assert_eq!(tree.depth, 128);
// Independently verify the complete left-associated shape, not just depth.
for _ in 1..128 {
let (left, right) = binary_children(tree, BinaryOp::And);
assert_eq!(right.kind, Kind::Boolean(true));
tree = left;
}
assert_eq!(tree.kind, Kind::Boolean(true));
assert!(
parse(&flat(129), &mut budget())
.unwrap_err()
.message
.contains("logical depth")
);
// The first argument is already a complete depth128 tree when parsing the
// second fails. Refusal tears down the held child without building Sum.
let malformed = format!("sum({deep}, @)");
let failure = parse(&malformed, &mut budget()).unwrap_err();
assert_eq!(
failure.message,
"Expected a valid reference or parameter name."
);
assert_eq!(&malformed[failure.span.start..failure.span.end], ")");
assert_eq!(number(&expression("1")), "1");
}
#[test] #[test]
fn significant_ratio_and_quantity_trivia_preserve_original_spans() { fn significant_ratio_and_quantity_trivia_preserve_original_spans() {
let source = "ratio // explanation café\n ( - // sign\n 3, // denominator\n 4 ) // measurement\n [@millisecond]"; let source = "ratio // explanation café\n ( - // sign\n 3, // denominator\n 4 ) // measurement\n [@millisecond]";
@@ -0,0 +1,20 @@
# Source8 containment and staged syntax evidence
This directory preserves branch-specific design and source-review evidence. It is not a live task ledger or an integrated/released capability claim. All copied Markdown files retain their original bytes, source pins and historical statements; `copied-evidence-sha256.json` records verified copy hashes.
- Input analysis candidate: `847b1caa7e75bb82f6ce5193c45bcfc45973ad1a`.
- Checked-source-save containment S0: `112e0d910bd2b2c9a191f29536fecb0fbc0177b0`.
- Private test-staged expression syntax S1: `02b9d5ce4c73d0446146b13b46a228e6fd4d7735`.
- Instruction/document-history merge: `e8a18a7cf97a9ebf4992f00521d06792d6c012e4`; implementation bytes remain those of S1.
- The commit adding this directory changes only tests and evidence: it strengthens exact mixed-precedence child topology and flat Binary128/129 plus completed-deep-child failure cleanup. It does not change the S0/S1 implementation.
Preserved reports:
- [Revised design](analysis-source8-repair-design-r2-20260913.md): root-selected syntax/oracle corrections, proposed resource calibration and implementation partitions. Resource numbers are experimental candidates, not measured adoption.
- [S0 independent review](analysis-source-save-guard-independent-review-20260913.md).
- [S1 independent review](analysis-source8-s1-independent-review-20260913.md).
- [Original S0/S1 source handoff](analysis-source8-s0-s1-handoff-20260913.md): exact scope and the original four S0/nine S1 tests. Historical `.local` paths and worktree statements in copied reports are provenance, not current branch status.
The added tests are `language::analysis_source::tests::mixed_precedence_and_grouping_have_independent_expected_child_topology` and `language::analysis_source::tests::flat_binary_depth_and_malformed_constructor_retain_bounded_completed_children`. There are now eleven staged syntax tests plus four source-save guard tests. The existing public checked-save regression remains unchanged and the public parser still rejects source8; its save refusal is expected until the complete adapter is implemented.
Only source inspection, rustfmt and whitespace/copy-hash checks were performed for this checkpoint. **Compilation, tests, timing/RSS and native acceptance are unrun here.** The independent reviews found no blocking source defect at their stated pins; they are not execution evidence. Root retains the compiler lease, final migration/version/resource adoption and integration/publication ownership. JSON/depth-switch behavior, model types, public source header admission and ordinary tokenizer implementations are unchanged.
@@ -0,0 +1,46 @@
# Independent review: S0 source-save containment
Reviewed exact Git blobs at `112e0d910bd2b2c9a191f29536fecb0fbc0177b0`, parent `847b1caa7e75bb82f6ce5193c45bcfc45973ad1a`. The author's changing worktree/S1 source was not used. No compilation, tests, application or tracked edits were performed. Findings below are source conclusions, not executed results.
**No actionable production correctness defect found within S0's guard-only scope.** The change prevents unreopenable or lossy generated source from reaching filesystem staging. It does not complete source8 or make this branch ready for ordinary source-save acceptance. One bounded public-entrypoint test coverage gap remains worth closing alongside S1; it does not invalidate the inspected guard implementation.
## Verified guard ordering and scope
At `persistence.rs:935–957`, public `save_checked` passes the existing DOCUMENT profile and unchanged depth-admission flag through `save_checked_profile` to the new production helper, supplying the real `language::format`. Thus the test seam contains the actual production guard, not a separate test-only reconstruction.
At `969–1000`, the helper validates the requested Project, selects source using the existing case-insensitive `.arcadia` extension rule, generates bytes, appends a newline only when missing, and passes those exact final bytes to public `decode_with_report_limited`. The effective byte limit is `profile.bytes` (DOCUMENT64MiB; smaller test profiles retain their exact limit). Other ParseLimits stay unrestricted because save_checked has no caller-supplied graph/record policy; S0 does not invent one. It then requires the decoded **entire Project** to equal the requested Project and refuses any migration report. The equality includes the new definitions catalog; no UUID mask, selected-field comparison or formatter-derived model expectation is used.
All those operations precede parent resolution and `NamedTempFile::new_in` at1006, writes/sync at1007–1008, conflict inspection at1009 and persist at1017. Decoder/validation failures return their existing typed error; lossy-output and unexpected-migration cases return distinct Invalid errors. There is no public decoder→save recursion introduced by this path. A generated source larger than the byte cap is rejected before decoding/IO, although the existing formatter still constructs its String before that cap check: this is not a new bounded-output allocator or a proof of arbitrary formatter stack/memory safety.
The `depth_128` parameter continues to govern the JSON encoder. Source admission deliberately uses the public reader's production setting rather than the private candidate-depth override, matching the requested public save/reopen invariant. `json_codec::DEPTH_128_VALIDATED` remains false; no deeper-profile acceptance is implied. Existing source parser nesting constraints and formatter behavior remain separate from this small guard.
## Sentinel evaluator assessment
The four new tests in `persistence/source_save_tests.rs` call `save_checked_profile_with_source`, injecting formatter output into the **same guard used by production**. They do not call the outer public `save_checked` function or exercise its real formatter output. This distinction should remain explicit in any test-result summary.
The evaluator is meaningful: each case tests an existing sentinel destination, a new destination and a destination under a missing parent; it requires the intended non-IO error, unchanged root membership, exact sentinel bytes and absence of new output. The missing-parent case is especially useful: moving NamedTempFile creation ahead of decoding would yield Io and fail the expected-error assertion even if temporary-file Drop would otherwise hide creation in the existing-parent cases. Inspection confirms no other filesystem effect precedes the guard.
- Malformed output (`arcadia`) checks public parser refusal, rather than manufacturing an arbitrary error.
- The lossy-output case adds a valid unused QuantityKind definition while injected source preserves familiar ID/name/elements and omits the definition. Full equality must reject it with the specific loss error. This catches an implementation that compares only the old graph fields. The equality check intentionally precedes migration refusal, so a schema7 decoded candidate can expose catalog loss distinctly.
- The equal-project legacy case reaches the separate migration refusal. Ordinary source7 decoding yields the current-schema8 Project with a migration report; equal meaning does not excuse emitting a legacy source representation.
- The newline test puts the unterminated UTF-8 source exactly at the byte cap, forcing the appended newline to exceed it. A preterminated version at the new exact cap reaches migration refusal instead, catching either checking before append or appending twice.
**Coverage note, not a demonstrated production bug:** none of these new sentinel checks protects the outer public call wiring against a later bypass of the shared helper. The existing public regressions at `review_regressions.rs:454,472,490` are useful, but all iterate `.arcadia` before JSON and currently unwrap the first source save. At S0 they are expected to stop at that first save rather than reaching their JSON/conflict cases. Consequently neither passing these four new tests nor the still-failing public regression establishes current JSON/conflict execution coverage. The smallest later check is one public save_checked end-to-end invariant/sentinel case using the real formatter, plus separate existing JSON/conflict cases so a source failure cannot skip them. After S1, retain the fault-injection tests and run the ordinary public save/update/no-op/conflict/deletion regression with source and JSON both observed. Do not weaken the public regression to accept permanent source failure.
## JSON, conflicts and integration limitation
The only production diff moves existing save logic into the private shared helper and adds the source-only check. JSON still calls the same encoder with the same profile/depth arguments. Existing newline, temporary write/sync, exact-byte conflict matching, missing-file Conflict and persist code are unchanged. Source admission now takes precedence over inspecting a stale destination; that is necessary for containment and preserves the external bytes, but it means S0 cannot demonstrate source Conflict for an unreopenable formatter output. This is source-based invariance, not a test pass.
At this exact commit, `language::format` writes schema8 while the language parser still accepts only3–7 (`language.rs:1410`). Therefore ordinary `.arcadia` saves remain unavailable, now safely returning an error before a destination is created/replaced. The previously executed847 regression failed after writing unreadable source; S0 is expected to fail its first unwrap instead. That changed failure position is a **prediction until executed**, and is containment, not repaired source8 round-trip capability. S1 must supply the complete current source representation/parser before normal source saving can be accepted. No JSON128 or broader analysis acceptance follows from this review.
## Exact input identities
| Git blob path | SHA256 |
| --- | --- |
| `crates/oa-core/src/persistence.rs` | `d7b33495e429a0bff65782be4f5295f8fedbd4f72ec394250919bb80ccf79d9e` |
| `crates/oa-core/src/persistence/source_save_tests.rs` | `a8eb528ac8f96c97292124409908476c25504303b11bd30b4c0358412e88edda` |
| `crates/oa-core/src/persistence/json_codec.rs` | `2aa2c52eea794acb85744440192df9ef5193160ad6ebd1febaa824e144aadd90` |
| `crates/oa-core/src/language.rs` | `b65098321ecf79e4dc1182a7fab7bafeec0a238bb56153e81782f04fab838558` |
| `crates/oa-core/tests/review_regressions.rs` | `f154e5c88d35e31512e2b07733a1327ea3e2fcfd1ef8eb23066a4e9573f387c1` |
The commit changes only persistence.rs and the new four-test source file. No S1 bytes were reviewed or endorsed. Execution and parent integration remain pending.
@@ -0,0 +1,292 @@
# Source8 persistence repair: r2 implementation proposal
2026-09-13. Author: Codex-956atf /root/nested_call_design. **Proposal only; root owns adoption, version/migration and combined acceptance.** Read-only investigation of isolated `.local/analysis-core-worktree-20260911`, exact clean commit `847b1caa7e75bb82f6ce5193c45bcfc45973ad1a`. No source changes, compiler, tests, UI or publication were performed. This report is outside that worktree.
This revision preserves the original design at `analysis-source8-repair-design-20260913.md`, SHA256 `db2e2662e5b25d24ca9a6a9cea1d7f97b18e714ffcd052e9dfc387b5fe7b3089`, and responds to `analysis-source8-repair-independent-review-20260913.md`. Root has selected significant-token literal trivia, independently specified display names, call-site-qualified argument spans, comparison-child parentheses and **short declaration words only**. Those choices are the r2 contract. The resource values/calibration and staged implementation partition below remain concrete proposals for root acceptance before coding.
## Outcome and boundary
Implement one typed source adapter for the existing neutral definition catalog and all three existing analysis element kinds, together with token-preserving source updates and a public-decode equality guard before checked-save replaces a destination. Keep schema8; do not introduce a further version or enable `DEPTH_128_VALIDATED`. Header acceptance alone is neither a repair nor an acceptable intermediate release.
The slice is complete for persistence of the candidate's **existing local static analysis vocabulary**, including every Expr variant. It does not add an evaluator, imported package resolver, native forms, historical analysis facets or unsupported transition semantics. It must not be called complete source/API/native parity or evaluated engineering behavior. The accepted architecture already requires those later capabilities.
Authoritative inputs read:
- Root `harness/release-analysis-checked-save-20260913.md`: actual public checked-save regression failed at decode after successfully writing source8. The source experiment is 0 passed/1 failed, not a private JSON-codec failure.
- Candidate `harness/DECISIONS.md:323,380`: accepted property-owned bases, complete-case overrides, shared checks, pure scoped calculations, exact rational AST preservation, closed Local environment, schema8 allocation, frozen older envelopes and bounded decoding.
- Candidate `harness/release-analysis-production-integration-plan-20260911.md`: complete response-budget vertical slice and typed source-adapter direction. Its earlier references to schema7-only production and unimplemented DTOs describe the earlier input; they must not replace the actual 847 source below.
- Candidate `PROJECT_GOAL.md:631–671`: equivalent text/graphical representations, human/AI editing, Git-friendly low-noise text, quantities/units/calculations and an explicit API.
- [Issue7 comment785](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/7#issuecomment-785), read through Gitea MCP: Scenario peer owns typed optional Diagram payload, layout/reference/source-row changes and gesture feasibility; root owns generic codec, aggregate limits and final version/migration. Neither unfinished contribution may inherit the other's broken version contract.
## Verified implementation facts
| Actual candidate source | Fact and repair implication |
| --- | --- |
| `model.rs:9`; `analysis/types.rs` | Schema8 already exists. DefinitionBody is QuantityKind/Unit/Calculation; DefinitionRef has only Local. ParameterId belongs to a calculation, not global identity. Keep the wire/model types unchanged. |
| `analysis.rs:17–23`; `analysis/admission.rs:10,66,95,469` | Logical depth128, closure65536, list1024, calls32 and a provisional aggregate static-event ceiling already exist. Admission checks names/parameter IDs, pure calls, declared types, all cases and dependencies. Reuse this admission; do not typecheck a second AST independently. |
| `language.rs:273,593,813,1353,1408,1652` | DeclKind has no definition variant; formatting omits definitions; generic values pass through serde_json::Value; source admits3–7 and rejects analysis elements. A required-body constructor cannot be obtained through ElementBody::new. |
| `analysis_numeric/exact.rs:76–124` | Exact has an existing canonical literal writer and metered literal parser. Negative Number literals print `ratio(-n, d)`; leading minus belongs to Unary Negate. Decimal source may normalize to a rational without losing the Expr shape. |
| `source_project.rs:92,780,815,1122,1572,1662` | SourceIndex has declaration/top-level field spans, but no typed expression/parameter paths. Updates recompile their public input index before trusting it. Definition IDs currently fall through the record-class test to the node-placement category and canonical ownership to artifacts. Both need explicit definition handling. |
| `source_project/source_migration.rs:4` | Existing migration makes token edits, changes the version, preserves module/trivia ownership, then recompiles and compares the whole migrated Project. Extend that route; do not flatten older authored bundles. |
| `migration_v7.rs`; `persistence.rs:178–193,449–486` | Frozen schema5/6/7 top-level JSON envelope already exists, rejects analysis kinds and adapts to an empty definitions catalog. JSON8 requires the current catalog. These are implemented candidate facilities, not work to re-create. |
| `persistence.rs:952–979` | save_checked_profile validates the Project, formats source or encodes JSON, writes/syncs a temp file, compares expected original bytes and persists. The source branch does not first prove public reopening equality. |
| `source_files.rs:819,854` | Modular save already recompiles the exact candidate bundle before its checked filesystem transaction. Retain this stronger bundle boundary; a flat source save is not a substitute for it. |
Read-file SHA256 identities, in addition to the exact commit: types `27363b1755f3c0943685a9837c69e010857811d4f67a0751b20efdd9c1707aa3`; language `b65098321ecf79e4dc1182a7fab7bafeec0a238bb56153e81782f04fab838558`; source_project `1394370bcc5908873b830c2c11312e6e1611af41f46c4f43ca0a6bcb713bb466`; persistence `eda5e697adb29a8bf38b0d367099bd23267455e4792f97607e13dc0b232a3476`.
## Candidate comparison
| Candidate | Preservation and cost | Decision proposed here |
| --- | --- | --- |
| Source-save fail-closed guard only | Small immediate containment; an ordinary schema8 save would still fail because no public source8 reader exists. No low-noise analysis authoring. | Land only as a safety boundary within the repair; do not present it as completed persistence. |
| Full externally tagged record/Expr notation using generic JSON-shaped fields | Could preserve the model after substantial changes for exact atoms, reference domains and maps. It exposes nested type tags and UUID-key machinery in every formula, and would require a second later text redesign. Generic field-name whitelists still cannot solve scoped keys. | Reject as the user-facing canonical language. The existing JSON API remains the machine interchange. |
| Typed declarations and expressions lowering directly to existing model types | Adds one bounded grammar and typed editing spans, supports the accepted readable fixture, and avoids speculative evaluation/package architecture. Parser/writer/index/update must be delivered together. | Recommended smallest complete compatible source slice. |
| Store formulas as strings or hide analysis in an opaque blob/sidecar | Smaller apparent parser work but breaks shared type/reference editing, traceable diagnostics, exact AST parity or single-model ownership. | Reject. |
The typed-adapter direction and semantics are already accepted; the exact concrete grammar below, canonical module names and resource implementation details are proposals for root/critic ratification.
## Concrete grammar contract
Keep existing project/declaration headers, aliases, explicit UUIDs, includes, descriptions and ordinary OA/SA grammar. Add neutral `quantity_kind`, `unit`, `calculation`; canonical layer words `oa_property`, `oa_check`, `oa_analysis` and their `sa_` counterparts map to EngineeringProperty, AnalysisCheck and AnalysisCase. **Only these short analysis declaration words are admitted.** The current long derived names (`oa_engineering_property`, `oa_analysis_check`, `oa_analysis_case` and SA counterparts) have never been admitted by this candidate's reader and must not be registered as speculative synonyms. Exclude those six derived entries from generic declaration-kind generation; formatter and tolerant outline use the short words consistently. These are source words, not renamed Kind variants or a new wire schema.
New analysis words/operators are contextual. Do not make previously legal architectural aliases globally invalid merely by adding expression keywords. Quoted names/descriptions and all existing descriptive expression/unit strings retain their previous meaning and bytes.
Required interpretation-bearing fields:
- Quantity kind: `dimension { time 1; }; algebra Linear;` or `algebra Affine(@difference_kind);`. Axis keys are the seven existing SI axes (`length`, `mass`, `time`, `electric_current`, `temperature`, `amount`, `luminous_intensity`); omitted axes mean zero, duplicate/unknown axes reject, authored values are bounded signed integers. Canonical output orders nonzero axes in DimensionAxis::ALL order; zero is `dimension {};`.
- Unit: `kind @kind; scale <exact-atom>; offset <exact-atom>; symbol "ms";`. Scale/offset are Exact, never arbitrary Expr. The existing numeric/admission rules decide positivity, linear offset and affine compatibility.
- Calculation: zero or more ordered `input name id "UUID" : <type>;`, required `result <type>; body <binding>;`. Parameters are a Vec: preserve authored order and stable ParameterId; reject duplicate names/IDs within the calculation. Zero inputs are valid. Parameter names are existing valid identifiers.
- Property: required `subject @element_or_relation; type <type>; base <binding>;`; optional display unit is `display_unit none;` or `display_unit @unit;`. Resolve subject through the complete typed global table, yielding exactly Element or Relation; a definition/diagram/analysis element is not an inferred subject. Canonical output makes display_unit explicit; omission may mean None, never inventing an analysis type, subject or base.
- Check: `subject none; documented_constraint none; predicate <binding>;`. The two optional links may be omitted with None meaning; the predicate is required even when `unbound`. Existing admission checks same-layer constraint/subject kinds.
- Case: `subject none; base ModelBaseline; overrides { @property = <binding>; };`. Base and overrides field are required; `{}` is explicit empty. An absent map entry inherits; present `unbound` masks the base. `clear`, `none` or another Case is not a Binding/base value.
- All three analysis elements are roots. Reject lexical nesting under another element and non-None parent. Neutral definitions have no layer/parent. Common description may be omitted as empty, as in existing declarations; no required semantic value comes from a factory default.
Types have exactly these forms: `Boolean`, `Number`, `Quantity(@kind)`, `List(Boolean)`, `List(Number)`, `List(Quantity(@kind))`. Nested List and invented primitive types reject. Constructor punctuation does not create additional model nodes.
Binding is `unbound` or one expression. Expression vocabulary covers every existing Expr, with no implicit casts/folding:
| Source form | Actual model node |
| --- | --- |
| `true`, `false`; unsigned decimal/integer/scientific atom; `ratio(-3, 4)` | Boolean; Number(Exact). Bounded numeric-only reconstruction follows the literal-trivia rules below, then calls Exact::parse_literal; never JSON Number/f64. |
| `750 [@millisecond]` or `ratio(-3, 4) [@unit]` | Quantity with literal magnitude and Local unit. `(a + b) [@unit]` rejects; magnitude is not an Expr in the actual type. |
| `@property`; `$parameter` | Property(global ID); Parameter(scoped ID). `$` is admitted only inside a calculation body and resolves against its owner. |
| `[expr, expr]` | ordered List, including contextual empty list; no expression nesting shortcut for nested list types |
| `-(expr)`, `abs(expr)`, `not(expr)` | Unary Negate/Absolute/Not |
| `a + b`, `a - b`, `a * b`, `a / b`; `==`, `!=`, `<`, `<=`, `>`, `>=`; `and`, `or` | corresponding BinaryOp, with original left/right child topology |
| `if(condition, when_true, when_false)` | If, all three children statically checked |
| `@calculation(a: expr, b: expr)` | Call(Local definition, exact ParameterId argument map), names resolved in the **called** calculation, not the caller/global map |
| `sum(values, initial)` | Sum with an explicit initial expression; no omitted-zero convention |
| `power(base, -2)` | Power with signed integer exponent -16..16 under existing profile; no expression exponent |
| `as_kind(@kind, expr)` | AsKind; existing nominal/anonymous type rule decides validity |
Precedence from low to high: `or`, `and`, equality, ordering, additive, multiplicative, unary, atom/call. Binary groups associate left; equality/ordering chaining without parentheses rejects rather than inventing chained-comparison semantics. Calls and comma-delimited constructs each require their exact arity. Parentheses may group but do not create Expr nodes. The canonical writer parenthesizes enough children to preserve the precise tree, including a right child of equal precedence; it does not use algebraic associativity to remove meaningful grouping. Canonical unary Negate always prints `-(...)`; Number(-1) prints `ratio(-1, 1)`. Thus `ratio(-1, 1)` and `-(1)` remain distinct through round trips, as required by the existing Exact contract.
Comparison non-chaining applies to both equality and ordering as one family: a comparison child on **either** side of a comparison parent is parenthesized even when ordinary precedence would otherwise suffice. Required emitted/accepted discriminators are `(1 < 2) == true` and `(true == false) == true`; `1 < 2 == true` and `true == false == true` reject. Numeric associative structure is separate: `1 - (2 - 3)` preserves the right-nested tree; `(1 - 2) - 3` may canonically print `1 - 2 - 3` because the parser rebuilds that exact left-nested tree. The writer must not flatten `1 + (2 + 3)` to a different tree merely because arithmetic values would agree.
### Literal trivia and reconstruction
Grammar uses significant tokens, retaining every token's original byte span. Existing whitespace and `//` comments may occur between constructor tokens and before a quantity suffix. Thus `ratio (1, 2)`, `ratio(1, // rationale\n 2)` and `750 // measurement\n [@millisecond]` are valid. A decimal/scientific atom is one indivisible token: `1e-2` is one atom; `1 e - 2`, `1e // note\n -2`, `1 . 2`, `1_0` and `0x1` are not alternative atom spellings. Adjacent tokens without an operator must not be concatenated into an atom.
For ratio, require exactly `ratio`, `(`, optional numerator `-`, one unsigned integer token, `,`, one unsigned denominator integer token, `)`, ignoring trivia at token boundaries. The numerator sign may be separated from its integer by trivia; `+` is not admitted. Components cannot be decimal/scientific expressions or signed denominators. Validate component digit counts and total normalized-byte size **before allocating** a reconstruction buffer. Copy only the verified sign/digit token bytes into bounded `ratio(-n, d)` spelling, then pass that spelling to the existing Exact::parse_literal with the shared literal WorkMeter. No identifiers, comments, unit references, operators or arbitrary source fragments enter the numeric reconstruction. Exact performs the same existing numeric validity/reduction checks.
For unsigned decimal/scientific atoms pass the single original numeric token directly; its internal spelling is never reconstructed from multiple tokens. The same ratio/atom rule applies to scale, offset and Quantity magnitude. Retain the full original literal span plus punctuation/component spans for editing and diagnostics: the reconstruction is ephemeral numeric input, **not a source rewrite**. Alias-only changes to `750 // measurement\n [@millisecond]` change only `millisecond`; parameter/property/definition renames leave ratio comments and original decimal spelling byte-identical. When an actual literal value changes, preserve its internal comments as local enclosing annotations under existing SyntaxUpdate policy.
Every raw byte, including trivia, is charged by the source scan budget; every lexical token and ratio component is charged before syntax allocation; reconstructed numeric work is charged only once by the shared numeric meter. `MAX_LITERAL_BYTES` limits the bounded numeric spelling, not an arbitrary amount of comments: original trivia remains independently bounded by whole-source bytes/tokens/scan work. A huge ratio comment therefore cannot cause an unbounded reconstruction buffer. Test accepted and rejected trivia at parse, canonical roundtrip and alias-only update boundaries.
## Concrete valid example under this proposal
This is proposed source8, **not a claim that the current parser accepts it**. It uses the same17 UUIDs and typed bindings as `analysis/tests.rs:101`, **with explicit display-name changes**. The existing fixture helper sets each non-project name equal to its alias, so its Project cannot be used unchanged as this example's equality oracle. The canonical sparse-dimension and override block terminators above remove ambiguity in the earlier illustrative fixture. Descriptions may be empty/omitted.
```text
arcadia 8;
project response_budget "Response budget" id "4210b9f2-7046-47f0-aa3b-153f626a579d" {
quantity_kind duration "Duration" id "188b911c-5f8b-431c-9eea-2d41f3953767" {
dimension { time 1; }; algebra Linear;
}
unit second "Second" id "534f4672-1ba5-4157-8b0a-b9cf41d0a3ce" {
kind @duration; scale 1; offset 0; symbol "s";
}
unit millisecond "Millisecond" id "39053f37-fec9-4708-9f0a-3959d223f891" {
kind @duration; scale ratio(1, 1000); offset 0; symbol "ms";
}
calculation combine "Combine durations" id "fd7ce7ab-f789-40bc-b699-0c37f1b6902f" {
input a id "28ff40b0-1b8f-4735-8536-5a6ab21b56cb" : Quantity(@duration);
input b id "7a47cc1e-8ae5-4551-b2d8-8a68108e0976" : Quantity(@duration);
result Quantity(@duration); body $a + $b;
}
oa_activity respond "Respond" id "cabec6a1-7d5d-48f3-8d2d-a7fc8cf78f59" {
oa_activity receive "Receive" id "4ab9a8a7-7552-488d-9862-e14b89e7b353" {}
oa_activity dispatch "Dispatch" id "6df991ba-4c5e-4535-99e2-524ae274ee8b" {}
}
oa_property receive_time "Receive time" id "63a931c2-675a-4fce-89e5-04361cb99450" {
subject @receive; type Quantity(@duration); display_unit @millisecond;
base 750 [@millisecond];
}
oa_property dispatch_time "Dispatch time" id "c33fc772-2a60-4044-9af9-73c61c563ace" {
subject @dispatch; type Quantity(@duration); display_unit @second;
base 1 [@second];
}
oa_property total_time "Total time" id "bd5b68e8-65fc-4e35-9071-a73061316a4b" {
subject @respond; type Quantity(@duration); display_unit @second;
base @combine(a: @receive_time, b: @dispatch_time);
}
oa_property limit "Allowed duration" id "437bedbb-2cf4-44ee-a139-6563cc5086ec" {
subject @respond; type Quantity(@duration); display_unit @second;
base 2 [@second];
}
oa_check budget "Response budget" id "bac0de99-9d2f-4e53-9158-d424ecac61bf" {
subject none; documented_constraint none; predicate @total_time <= @limit;
}
oa_analysis degraded "Degraded radio" id "38036c65-16d4-449c-837b-91eb141c1d93" {
subject none; base ModelBaseline;
overrides { @receive_time = 1250 [@millisecond]; };
}
oa_analysis unmeasured "Unmeasured radio" id "99d3e09a-0ec3-4194-9dd4-9f3de6625cde" {
subject none; base ModelBaseline;
overrides { @receive_time = unbound; };
}
}
```
The expected Rust Project builder must independently assign these **14** display names, keyed by the already specified UUID/alias; this table is part of the oracle, not derived by parsing the example:
| Record alias | Required name |
| --- | --- |
| duration | Duration |
| second | Second |
| millisecond | Millisecond |
| combine | Combine durations |
| respond | Respond |
| receive | Receive |
| dispatch | Dispatch |
| receive_time | Receive time |
| dispatch_time | Dispatch time |
| total_time | Total time |
| limit | Allowed duration |
| budget | Response budget |
| degraded | Degraded radio |
| unmeasured | Unmeasured radio |
Project name remains `Response budget`; project and all record descriptions in this worked example are empty. Parameter names remain `a`/`b` and are not global display-name rows. Build these exact headers plus the independent existing typed bodies and IDs; never ignore/normalize names or obtain the expected Project via the new parser/formatter. Assert these name fields explicitly before the first parse comparison so accidentally reusing fixture() unchanged fails clearly.
An SA twin changes only the architectural bodies/layers and declaration words, with fresh global IDs; definitions remain neutral. Validate exact typed equality against a separately constructed actual Project, not against data produced by the source parser/writer. Baseline 7/4 and degraded 9/4 are the existing future evaluation expectations; this persistence slice verifies bindings/graphs and cannot claim it has evaluated those results.
Concrete invalid mutations, each rejected without replacing the accepted model/source/files:
- `arcadia 7;` with any of these six declaration kinds, including empty bodies, or any explicit definition-catalog attempt. Older input must not quietly acquire/discard new fields.
- Remove a property's subject/type/base, calculation result/body or case base/overrides; use `base none`, `base @degraded` or `overrides { @receive_time = clear; };`.
- Duplicate `a:` call arguments or override `@receive_time` entries; duplicate parameter ID in one calculation; use `a:` plus another name resolving to the same parameter. Reject before BTreeMap insertion can overwrite data.
- `@combine(a: @receive_time)`; extra `c:`; `$a` outside the calculation; a calculation body reading `@receive_time`; a Quantity type referring to `@second` rather than a kind; a quantity suffix referring to `@duration` rather than a unit.
- `ratio(1, 0)`, `power(2, 17)`, unknown dimension axis, dimension exponent33, a negative scale, a linear unit with nonzero offset, or nested `List(List(Number))`.
- `if(true, 1, @duration)`, a same-layer property cycle only introduced by one stored case, or OA property reading an SA property. Unused branches do not exempt static admission.
- Moving a property lexically inside `respond`; a definition placed under an activity; a subject naming an analysis case. No containment inference repairs these.
- Global reidentity map from a definition to a node-placement record; orphaned argument/override target after deletion without explicit same-batch repair.
## Source pipeline and exact editing
1. Extend `language::DeclKind` and declaration parsing with a typed analysis payload branch. Keep raw numeric spans and unresolved typed reference spans while parsing. Do not first resolve analysis fields through the generic Ast::resolve/string-key whitelist. Construct required ElementBody::shared_in records and Definition directly, then run existing complete Project admission. Keep old declaration parsing untouched outside the necessary lexical punctuation support.
2. Resolve in bounded passes: collect all global declaration IDs/aliases and all calculation headers/parameter vectors across reachable files; then resolve definition types and analysis bodies/call argument names using those tables. Forward references, forward calls and cross-file definitions work independent of declaration order. Pure-calculation/body scope is an explicit owner, never a global parameter registry.
3. One typed source representation supplies strict lowering, canonical writing, EditValue/path extraction and source diagnostics. It may retain bounded temporary syntax nodes/spans, but cannot become a second persistent model/evaluator. Include declaration/header/field spans, ordered parameter identity/name spans, override target spans, called-definition/argument-key spans and every Expr child path. Preserve existing top-level SourceIndex consumers; extend with typed locations rather than changing their key meanings.
4. Writer walks typed definitions/analysis bodies directly, including Exact::canonical_literal. It does not route those bodies through serde_json::to_value/payload/unwrap. Canonical calls print named arguments in the declared parameter order; parameter vectors retain their own order; override entries sort by stable target identity. Definitions get an explicit record category distinct from node placements in source identity correspondence.
5. In `SyntaxUpdate`, match parameter declarations by `(calculation ID, ParameterId)`, overrides by property ID within their case, and expression children by typed owner/field/path. A call argument occurrence is keyed by **`(owning declaration ID, binding field including override target if any, Expr call-site path, called calculation ID, ParameterId)`**; the callee/parameter pair alone is insufficient. An alias-only edit rewrites only the relevant alias token, including inside a call, unit/type suffix and override key. Parameter rename edits its name, `$` uses and argument labels of every call site to that calculation only. Global Reidentify uses existing typed analysis reference visitors, never rewrites a ParameterId just because its UUID equals a global ID, and never treats old historical capsules/strings as live refs.
6. Retain unchanged authored expression bytes, including decimal/scientific spelling, whitespace and comments. For changed subtrees replace only the smallest changed syntax; carry or retain comments from removed nodes as enclosing annotations following existing source-update conventions. Whole-field regeneration on every alias rename is insufficient. Avoid canonicalizing unrelated parameter order, includes, line endings or modules during one edit.
7. `format_bundle` gives definitions a deliberate neutral default file (proposed `definitions.arcadia`), and new layer-root analysis records a category file (proposed `oa/analysis.arcadia`, `sa/analysis.arcadia`). These are defaults only. Existing authored ownership stays fixed; alias/parameter rename never relocates files. No subject-derived ownership or implicit nested property is introduced. Keep existing file-count fallback/path/case/include validation.
8. Continue to recompile exact updated bytes, reject stale/forged indexes and require whole Project equality before source acceptance. Session/PreparedProject/Document history naturally retains the new bytes/records only if this boundary remains intact. Retain new navigation/rename consumers' exact declaration recognition and token ownership during later root composition; tolerant highlighting must recognize new punctuation without diagnosing every incomplete keystroke as a persisted-model admission.
Missing UUIDs may use the existing explicit stabilization workflow, including parameter headers, but the first compile must insert the assigned ID into the owning document and rebuild exact indexes. Never independently assign a fresh parameter UUID at each later compile. Canonical source always contains IDs. Reuse of one ParameterId in two different calculations is valid; all update/index keys must include its owning calculation.
Mandatory occurrence discriminator: `@combine(a: @receive_time, b: @dispatch_time) + @combine(a: @receive_time, b: @dispatch_time)` has four distinct argument-key spans, despite only two callee/ParameterId pairs. Rename parameter `a` to `first` and verify both `a:` tokens and all `$a` uses change while the two call-site comments, `b:` tokens and unrelated same-name parameters remain exact. Also swap `a`/`b` names in one complete EditDefinition with stable IDs: build all edits against original spans before applying, so a sequential string replacement cannot collapse the names or change argument associations. Canonical ordering may follow the current parameter Vec, but alias-only updates must not reorder authored call arguments.
The shared lexer is a necessary upstream change, not an optional highlighter follow-up. At `language.rs:199–224`, current numeric token classification already invokes serde_json::Number, greedily absorbs `+`/`-`, and classifies `$`, arithmetic/comparison operators as Error; strict_tokens then rejects before a typed adapter could run. Introduce bounded raw decimal/scientific lexing without numeric conversion, separate operators with longest-match handling (`<=`, `>=`, `==`, `!=`), `$`, and correct exponent-sign handling. `1-2`, `1e-2`, `ratio(-1, 2)` and `-(1)` must tokenize distinctly. Existing generic numeric fields still use their original finite JSON/i64/f64 admission at consumption, including signed coordinates; lexing must not expand that field grammar to arbitrary Exact values or silently accept malformed `1e`, `1_0`, `0x1` or previously rejected `+1`. Preserve comments/string escaping and byte-span/Unicode diagnostics. Run old coordinate/message-value/nesting token tests alongside the new exact atoms. A source-mode switch that tokenizes the same included declaration differently from its entry is unsuitable.
## Resource and older-version contracts
Keep existing whole-file/bundle byte, include, graph-object, nested-record and ordered-reference limits. Count definition records, parameter declarations, all stored Expr nodes, call arguments, override entries and lists across the complete reachable bundle before allocation or expensive resolution can multiply work. Do not reset counters per module, field, case or call. Existing `nested_record_count` is a final model check, not sufficient preallocation admission.
Use a bounded iterative typed-expression parser/writer/edit visitor or an explicitly bounded stack strategy with adverse-input evidence. Logical depth counts Expr edges; source parentheses/call punctuation need a separate syntactic-depth/node budget so millions of redundant groups cannot hide below Expr depth1. Existing generic value depth64 remains for existing records; it must not silently reduce typed Expr128 or be globally removed. Pair depth128 success with129 rejection and flat wide lists/call maps and partial-input cleanup. Resource rejection must leave no partial Project or accepted source.
Exact atoms call the existing metered parser after raw-span/digit checks. Unit scale/offset and every binding share one aggregate literal-admission meter across the entire parse, distinct from evaluation fuel and static graph events. A fresh WorkMeter per literal is not aggregate protection. The concrete candidate limits and calibration below replace r1's unspecified ceiling; they are proposed, not measured or adopted. Do not claim private JSON Scope node metering provides this source literal meter.
### Proposed source8 admission profile and calibration
Use one private `Source8Budget` for a **complete reachable bundle parse**, passed explicitly through every file/declaration/body. Public ParseLimits retains its existing four fields; no public struct-field/API break is needed for this slice. Production entry points derive the hard source8 profile below, and a crate-private test entry point may lower every ceiling. Existing caller byte/graph/record/reference restrictions remain independent and can only lower acceptance. `UNLIMITED` does not disable hard source8 limits.
| Counter | Candidate ceiling | Charge point / connection to caller policy |
| --- | --- | --- |
| Raw UTF-8 bytes | `B = min(max_source_bytes, 64 * 1024 * 1024)` across reachable files | Sum checked before token Vec or decoded-string allocation; include comments/trivia. Preserve existing ProjectLimits file/include restrictions. This hard cap applies to source8 candidates; do not accidentally redefine older standalone-source transport policy. |
| Lexical tokens | `min(B, 1_048_576)` including whitespace/comment tokens | Reserve before every token push; zero-byte EOF marker is excluded. A single long comment is byte-bound even though one token. Failure stops tokenization immediately. |
| Analysis syntax entries | `min(262_144, max_records)` | Reserve before each Expr node, parameter row, call argument entry, override entry, definition body, analysis body or type constructor allocation. Count duplicate attempted entries before rejecting; never insert then discover budget overflow. This bounds temporary syntax too, not just final accepted model records. |
| Additional global records | Existing max_graph_objects/max_records | Definition headers are graph records; all old graph/record checks remain. Existing final check_model still verifies the completed model. Temporary syntax entries are a separate counter, not a renamed/double-interpreted final record count. |
| Definition/argument/reference occurrences | `min(500_000, max_step_references)` | Count each source analysis reference occurrence before resolution: type/kind/unit/property/subject/constraint refs, parameter uses, call target and each argument/override target. Combined charge also retains all ordinary ordered-step references, so files cannot each reset the transport reference budget. |
| Active typed syntax frames | 256 | Parentheses, lists, function/call/type constructors and ratio parsing enter explicit frames; reject before pushing frame257. Logical Expr depth128 remains separate. No native-stack recursion is justified by this counter alone. |
| Exact numeric work | 50_000_000 numeric units per complete parse | A single WorkMeter constructed with `WorkLimits::new(0, 50_000_000)` feeds all Exact::parse_literal calls. No evaluation visits are made. This is the existing maximum numeric reference ceiling used as a **candidate admission ceiling**, not an evaluated-work claim. Caller byte/record/reference caps do not scale this number; they independently reduce the possible workload. The private test profile may lower it. |
| Static analysis work | Existing AdmissionLimits::REFERENCE, one million events | Run the actual admission once after construction, with its own recorded usage; no per-case reset. This proposal does not alter its charging rules. |
Per-atom limits remain the existing 1234 component digits, 2500 normalized literal bytes, 4096-bit components and decimal exponent -128..128. Source8 scope must be known before broad token allocation: the entry's bounded header scan decides the version; included fragments inherit that one checked version and budget. Fragments must not select different resource policy by local spelling. Source3–7 migration to source8 must validate its produced candidate under the same source8 limits before acceptance/save, while preserving the previous accepted model on limit failure.
Parsing must remain linear in scanned raw bytes/tokens plus explicitly metered resolution/numeric work: one raw lexical scan, bounded lookahead, no repeated scan of a whole expression per descendant. Parser, resolution and writer traverse explicit worklists and preserve first-failure location. One compile may perform its existing bounded stabilization/migration reparse; each entire candidate parse gets a fresh budget, never each field/file. Instrument the compile pipeline to establish its fixed pass count (initial, stabilized and migrated candidate; expected at most three), and reject any design that recursively reparses until success. Updates may independently check old/new whole candidates; record those passes separately so measured totals do not hide them.
Numbers above are deliberately falsifiable candidates. They must not silently become advertised production limits merely because parser tests pass. Proposed Windows calibration before enabling source8:
1. Build an isolated actual-core test/probe executable after the lexer/typed adapter exists, in debug and optimized profiles. No compiler is authorized by this report. Invoke the same public compile/format/update/decode/save routes; a parser-only allocation microbenchmark is insufficient. Record exact commit, lock/toolchain, command, input SHA, effective counters, first failing byte/path, elapsed time, process peak working set and private bytes, exit and stderr. Use an external Windows process sampler; include process baseline and absolute peak, rather than subtracting shared pages to invent an allocator measurement.
2. Deterministic workload families: (a) valid17-UUID model with exact display names; (b) near-limit legal flat Expr/list/call/override inputs split over 1/8/64 includes; (c) many near4096-bit coprime ratio components and consecutive-Fibonacci numerator/denominator pairs to stress reduction, with equal-value trivial integer controls; (d) 256/257 nested redundant groups around a depth1 Number plus logical128/129 valid typed trees; (e) token-limit comments/whitespace and raw-byte-limit long comments; (f) repeated duplicate argument/override floods, unknown refs and truncation at every structural boundary of a wide/deep input. Keep generated numbers within exact-digit/bit limits before blaming aggregate work. Each failed case also measures Drop/cleanup and verifies a fresh subsequent parse succeeds.
3. Use geometric sizes around the proposed ceilings (1/4, 1/2, 1, then one entry beyond) with three fresh-process repetitions. For numeric exhaustion, choose a lowered test ceiling equal to the successfully measured deterministic cost of a specific prefix, then a prefix-plus-one atom; assert no later literal/resolution visit. Split identical declaration streams across includes and verify the same aggregate numeric/syntax usage and failure identity (path changes are expected). Order-dependent first failures must follow documented include/declaration order, never hash iteration.
4. Functional acceptance: the worked model and a **valid near-ceiling counterpart for each independent bound** succeed, exact bound+1 rejects before the offending allocation, no panic/stack overflow/hang, exact whole Project and untouched-source/disk sentinels hold, recorded counters cannot exceed their ceilings, and repeated runs have identical logical usage. A benchmark where every large case is rejected by an unrelated lower limit does not validate the intended bound; lower other dimensions or isolate the private budget test to reach it.
5. Proposed desktop resource targets for root ratification: worked fixture compile/format/update each at most100ms optimized median; any one bounded compile/admission attempt including failure cleanup at most2s optimized and10s debug; peak private bytes at most512MiB and peak working set at most768MiB for the probe process. Treat these as **candidate usability ceilings**, not observations. Record slower/ larger results; adjust one candidate bound at a time and rerun affected near-limit successful/failing controls. Do not merely loosen a threshold to relabel failure, reduce Expr128 or hide the elapsed reparse passes. If large valid models need more capacity, root must compare measured responsiveness/memory tradeoffs before accepting a higher profile.
This calibration is a stop/go decision for source8 exposure. JSON optimized RSS, its depth enable switch and complete native/API parity remain separate gates even if these source measurements pass.
JSON8 public depth remains governed by `DEPTH_128_VALIDATED == false`. This source repair must not toggle it or treat successful shallow fixtures as deep transport acceptance. Retain the public-codec fail-closed behavior; record any resulting deep source/JSON parity limit explicitly until the separate codec gates close. Static Expr128 is an accepted semantic contract, not evidence that every present transport already fulfills it.
Admit source8 only with complete typed support. For source3–7, preserve each existing field/constructor rejection and migration route, adapting an empty definition catalog and changing required version/default tokens only. Old schema5/6/7 JSON continues through the already implemented Project7 DTO, with explicit unknown/new-field rejection even for empty/null values. Do not add serde defaults to new-schema required fields. Existing source1/2 legacy paths remain explicit; do not rename their syntax or reinterpret descriptive expressions, DataField unit strings, message values or semantic baselines. Freeze ordinary OA/SA sources with comments/Unicode/CRLF and exact old JSON fixtures as migration controls.
In particular, source3 lowering at `language.rs:1836` currently constructs a legacy payload by serializing the current Project before invoking the old adapter. Audit this exact boundary against the frozen old envelope: newly present `definitions` must not leak into a closed old payload or be used to make new source3 fields acceptable. Construct the intended old envelope explicitly where necessary, preserving the existing migration's IDs and semantic defaults. Do not infer older-source acceptance solely from Project7 JSON tests.
## Checked-save before replace
After semantic/model-limit admission and bounded formatting of a `.arcadia` output, but **before creating/replacing destination content**, decode the exact generated bytes through the public source-capable limited decoder with the same effective document limits. Require decoded Project == requested Project and no unexpected migration. Return an ordinary located/typed failure on decode or mismatch. Do not compare only IDs, counts, selected analysis fields or a normalized analysis-free projection.
Only after that check succeeds: write/sync temp bytes, perform the existing expected-original-byte check and persist using the existing filesystem mechanism. Keep its conflict semantics; this proposal does not claim to eliminate all external writer races. Newline addition must occur before the decode/equality check so the checked bytes are precisely the returned/persisted bytes. Ensure all public save wrappers reach the guard. Leave bounded JSON codec/profile mechanics under root ownership; do not widen admission as a workaround for the source failure.
The modular source_files::save_project route keeps its compile-and-compare gate over the full exact bundle and existing transaction/conflict machinery. It must include definitions/analysis now that compile_bundle understands them. It must not flatten includes into the checked-save single-file API. Failures leave existing file bytes and file inventory intact; a no-destination save failure must not create the destination.
## Meaningful acceptance sequence, all proposed/unrun
1. Add `crates/oa-core/tests/analysis_source.rs` using the real17-UUID typed bodies independently constructed as in `analysis/tests.rs:101` **and the explicit14 display-name assignments above**; do not reuse unchanged fixture() or use formatter output as the sole expected input. Parse the explicit example, compare whole Project, format/parse again, compare whole Project and canonical text stability. Split the source into entry/neutral/OA-or-SA modules with forward includes, Unicode/CRLF/comments and equal string occurrences of aliases. Compile twice and compare exact bundles/indexes; test missing-ID stabilization separately. Retain an unused definition and empty/unbound calculation to catch accidental reachable-only export.
2. Exercise all Expr/AnalysisType/DefinitionBody variants with independently built valid typed projects: signed ratio versus Unary Negate, equal-precedence grouping, List/If/Sum/Power/AsKind, affine/difference kind, unit offset, scalar Boolean/Number and quantity/list calculations. Pair invalid examples above with exact located rejection and no mutation. Source persistence, not numeric evaluation, is the oracle for this gate.
3. Run source update transactions: change one property base, SetAnalysisOverride(Unbound), ClearAnalysisOverride and undo/redo; rename property/definition aliases; rename a parameter through EditDefinition while preserving its ID; reidentify a global definition whose UUID intentionally equals a scoped ParameterId; reorder parameters; delete a used definition with and without same-batch repair. Compare accepted whole Project to the independently applied core command, exact targeted source token changes, unrelated file bytes/comments and history. Case-only cycle rejection retains previous source/revision/history. Introduce two calculations reusing a ParameterId/name to falsify unqualified parameter rewrites.
4. Add safety tests at the save preparation boundary with deliberately unparseable and parseable-but-incomplete generated source: both must fail before replacement and retain existing sentinel bytes. Keep the seam private and exercise the same guard the real writer calls. Then execute the **unchanged** existing `checked_save_returns_exact_bytes_and_accepts_unchanged_external_content` against ordinary and populated projects for `.arcadia` and `.oa.json`, fresh/existing/unchanged external/different external cases, exact returned bytes and public reopened Project. Do not weaken the existing test or claim the prior failure vanished from evidence.
5. Reuse `source_project.rs` tests `model_updates_rewrite_cross_file_alias_uses_preserve_ownership_and_untouched_comments`, `alias_updates_touch_only_header_and_reference_tokens_even_with_comments_after_at`, `failed_update_preserves_candidate_and_rejects_stale_index`, `reference_list_reordering_carries_item_comments_and_alias_spelling_with_identity`, `project_admission_is_aggregate_and_precedes_global_resolution`, and existing source_files transactional-save/reopen tests. Extend representative fixtures; retain the original controls.
6. Reuse `analysis::tests::complete_real_model_roundtrips_and_exposes_explicit_effective_bindings`, `narrow_override_commands_preserve_missing_clear_and_history`, `global_reidentify_rewrites_typed_domains_but_preserves_scoped_parameters`, `calculations_are_pure_parameters_are_scoped_and_calls_bind_exactly`, `frozen_schema7_upgrades_without_interpreting_descriptive_metadata`, existing source migrations/nesting/review regressions and JSON codec tests. Add aggregate-source and half-parsed wide/deep failures; do not infer resource safety from shallow happy paths.
First bounded executable gate after root adopts and independently reviews the implementation:
```text
cargo test --locked -p oa-core --test analysis_source -- --test-threads=1 --nocapture
cargo test --locked -p oa-core --test review_regressions checked_save_returns_exact_bytes_and_accepts_unchanged_external_content -- --exact --test-threads=1 --nocapture
```
Run in a newly identified isolated target under the sole compiler lease, preserve failed inputs/logs before correction, then run affected source/migration/analysis/API persistence gates. Success means populated whole-model/source parity and safe replacement, not merely accepting `arcadia 8;`. Public API stored SourceBundle/model equality, older-version HTTP/CLI save/reopen and optimized codec memory remain separate retained obligations; Linux remains deferred.
## Ownership and decisions still requiring critique
Use new `language/analysis_source.rs` plus a small typed-source update module if needed; shared hooks are `language.rs`, `source_project.rs`, source_structure/source_migration and the checked-save guard in persistence. No model schema/type rewrite is needed. Root must partition those shared files against the Scenario payload peer. Compose typed Diagram payload handling independently, then make **one** final schema8/version/migration acceptance change; include an ordinary architecture diagram plus a populated analysis project plus Scenario-layout payload in a later combined round trip. Analysis source must not interpret Scenario view coordinates or grant gesture feasibility from finite-number parsing. Comment785 is the existing ownership agreement; no new Gitea claim/post was made here.
Root-selected r2 corrections are fixed: short words only, significant-token numeric trivia with bounded reconstruction, exact display-name oracle, call-site-qualified spans and comparison-child parentheses. Remaining adoption decisions: the proposed resource table and measured thresholds; exact private index representation/missing-parameter-ID insertion mechanics; and default neutral/analysis module filenames. The central semantics, schema8 allocation, no imported placeholders, precise reference domains and no header-only repair are already accepted constraints.
### Staged implementation partition before coding
No overlapping source writers. The root integration owner owns manifest/lib hooks, shared codec/version/migration policy, compiler lease and composition with Scenario work under comment785. Suggested bounded stages:
| Stage | Writer/files and explicit boundary | Review/exit criterion |
| --- | --- | --- |
| S0: checked-save containment | One writer: persistence.rs plus a private source-save guard test module; no JSON codec, language header/grammar, model or schema edits | Exact final-newline source bytes publicly decode under the same effective limits, no migration, whole Project equality before temp/destination write. Malformed and parseable-lossy private-seam inputs preserve existing/missing destination sentinels. Existing public regression remains unchanged and still fails until the parser is repaired. Root has now authorized this containment invariant independently. |
| S1: typed lexical/syntax contract and budget | One source-language writer: new language/analysis_source.rs and its lexical/parser tests, bounded source budget; only necessary shared language.rs tokenizer/parser hooks reserved to that writer | Strict/ tolerant tokens and existing numeric-coordinate controls; all Expr shapes/ratio trivia/duplicate keys; short declarations only; lowerable budget boundaries and cleanup. Do not enable the public schema8 header as an isolated patch. Keep modules private/source-only until S2/S3 compose. |
| S2: typed resolution and canonical writer | Same language owner or explicit sequential handoff: analysis_source.rs lowering/writing, language.rs DeclKind/format_declarations/required constructors; independent analysis_source.rs expected-model tests | Complete worked OA and SA model, definitions/all Exprs, exact fourteen names, independent whole Project/canonical roundtrips. This is the first point at which public source8 parser admission may be composed, together with S3 so editing/checked save are complete; still no release adoption. |
| S3: typed source indexes and updates | Source-update owner after S1 typed spans freeze: source_project.rs plus new source_project/analysis_source_update.rs, limited source_structure/default ownership hooks; no simultaneous shared language.rs edits | Qualified parameter/call-site/override matching, swaps/repeated calls, missing-ID stabilization, original-module/comment preservation, identity-map categories and whole-model command/update equality. Coordinate temporary language hook requests through its owner. |
| S4: final migrations and guarded save composition | Root: final source header version admission and older migration hooks, previously frozen JSON DTO compatibility, source_files full-bundle save and S0 guard integration; Scenario peer supplies only its owned typed Diagram/source-row delta | Ordinary old OA/SA migrations with exact comments, populated analysis8 plus Scenario payload composition, no header-only acceptance, all checked save destinations/reopen/history/ conflict sentinels. No schema bump beyond8 or codec switch change. |
| S5: execution and adoption | Root sole compiler owner; independent critic on immutable complete source pin | Focused source8/S0 tests then affected source/analysis/migration/full/strict gates, measured profile calibration above, explicit remaining deep-JSON/native/API limitations. Failed gates retain exact inputs/logs before correction. |
Stages are implementation leases/checkpoints, not independent feature releases. S0 can safely contain failure by refusing source save, but does not satisfy the user's persistence objective. S1–S4 must form one coherent reviewed source8 slice before enabling it in the shared application. Root should assign S0 first, then freeze typed syntax/index contracts for S1/S3; speculative evaluator/API expansion is outside every stage.
The most useful next action is independent critique of this complete source boundary, especially parameter identity versus source trivia, AST-preserving pretty printing and limit accounting, followed by one owned implementation slice. Further evaluator architecture research would not repair the demonstrated persistence failure.
@@ -0,0 +1,53 @@
# Source8 S0/S1 source handoff
2026-09-13, Codex-956atf /root/nested_call_design. Source-only work in `.local/analysis-core-worktree-20260911`, branch `codex/analysis-core-20260911`. The worktree is clean at **02b9d5ce4c73d0446146b13b46a228e6fd4d7735**. No compiler, Cargo, test executable, native UI or Gitea write was run by this worker. Root owns publication, integration and compiler leases. Latest fetched bff8014 AGENTS instructions were read; its documentation commits were not merged and the user-owned specification was untouched.
## Pins and design
- Original input: `847b1caa7e75bb82f6ce5193c45bcfc45973ad1a`.
- S0 containment: `112e0d910bd2b2c9a191f29536fecb0fbc0177b0`, two files, 187 insertions/4 deletions.
- S1 staged syntax: `02b9d5ce4c73d0446146b13b46a228e6fd4d7735`, six files, 1345 insertions, directly atop S0.
- Revised design: `analysis-source8-repair-design-r2-20260913.md`, SHA256 `3a5b96fd130e3859932ee25544b3ff2f6a21f0774f3b5849e546f9574b9dbf7d`. Original db2e266 design preserved. Root accepted the corrected syntax/oracle direction, candidate resource table for experiments and reversible default module names; measurements/profile adoption remain open.
## S0
The generated `.arcadia` bytes receive their final newline, then pass the public limited decoder with the same effective profile byte limit. Whole Project inequality and unexpected migration reject before NamedTempFile creation. The source-format function is injectable only through a private seam. Existing JSON encode, expected-original conflict and persistence code retain their behavior; no source8 header whitelist or JSON/depth change was added.
Four tests under `persistence::source_save_tests::`, all unrun:
- `malformed_generated_source_rejects_before_any_filesystem_staging`
- `parseable_source_omitting_a_definition_cannot_replace_the_whole_project`
- `equal_project_from_generated_legacy_source_still_rejects_migration`
- `generated_source_admission_checks_final_newline_under_the_effective_byte_limit`
Each checks existing/new/missing-parent destinations. Missing parent distinguishes decode-before-temp from a later temp creation that would fail with Io. The independently parsed legacy source equals the ordinary Project for the migration control; the lossy control adds an actual unused Duration definition while preserving ordinary headers/records. The original public checked-save regression is unchanged and is expected to remain failing at save until full source8 support exists. Containment is not persistence completion.
## S1
`language.rs` adds only a four-line **cfg(test)** module declaration. The old shared tokenizer, source3–7 parser and public source8 rejection are byte-identical in their implementations. The typed raw lexer is private and test-staged until the S2 consumer can integrate signed ordinary numeric consumption compatibly. This avoids exposing an unfinished global lexer/reader combination.
New `language/analysis_source` modules supply unresolved typed Binding/Expr syntax and exact source spans. They cover all13 existing Expr shapes, named arguments, scoped-name syntax, required constructor arity, literal-only quantities, explicit Sum initial, bounded Power, precedence and non-chaining comparisons. They do not resolve aliases/ParameterIds, validate purity/types or produce a Project. The parser uses an explicit task/value stack, with syntactic frame and logical AST depth checks. Accepted partial ASTs have depth at most128; ordinary owned-tree teardown remains bounded by that depth.
Raw numeric lexing never invokes serde_json::Number. Significant ratio tokens are checked and reconstructed into a bounded numeric-only spelling, passed to the existing metered Exact parser; full raw literal span and individual punctuation/component spans survive. Call argument occurrences retain unique spans even when their callee/parameter names repeat. Actual qualified SourceIndex entries and token-preserving command updates belong to S3; the span replacement test is a syntax discriminator, not a claim that production rename works.
An explicitly shared Budget aggregates bytes, lexical tokens (including skipped trivia), syntax entries, references and exact numeric units across fragment parse calls, with bounded active frames. Reservations precede token/AST/reference allocation, duplicate attempted call entries are charged, and resource exhaustion is latched. Candidate ceilings derive from the r2 table and existing lowerable ParseLimits; no measured resource adoption is claimed. Full declaration/graph/include/reference accounting and entry-version selection remain S2/integration work; expression-only budget tests cannot prove whole-bundle admission or optimized RSS.
Nine tests under `language::analysis_source::tests::`, all unrun:
- `significant_ratio_and_quantity_trivia_preserve_original_spans`
- `raw_numeric_lexing_preserves_exact_atoms_and_operator_boundaries`
- `unary_literals_precedence_and_comparison_children_keep_distinct_trees`
- `every_expression_shape_and_exact_constructor_arity_is_represented`
- `repeated_call_argument_occurrences_have_distinct_original_spans`
- `budgets_aggregate_across_fragments_and_latch_numeric_exhaustion`
- `byte_token_frame_logical_and_list_boundaries_have_nearby_successes`
- `partial_wide_and_deep_input_cleanup_does_not_poison_a_fresh_parse`
- `public_source_headers_and_existing_numeric_token_controls_are_unchanged`
They cover exact negative Number versus Unary, comparison left/right grouped children, 400-digit atoms, comments inside ratios and before units, repeated call keys, malformed arity/components, per-budget near successes/failures, numeric exhaustion across fragments and cleanup. They do not establish canonical source writing, source-edit preservation, S2/S3 functionality or CPU/memory timing.
## Performed and remaining checks
Rustfmt edition2024/skip_children was run on the two S0 Rust paths and six S1 Rust paths using the configured local toolchain; all invocations exited0. Git whitespace checks passed. No execution result is claimed.
Next: independent immutable-source review of S1 and root-assigned compiler gate selecting both test prefixes, retaining the unchanged public regression as the known incomplete-source8 witness. Fix findings in a new commit without changing old evidence. Then proceed to S2/S3 under explicit ownership, and root's final migration/version/resource acceptance. No merge of remote documentation history or force-push was attempted here.
@@ -0,0 +1,24 @@
# Independent S1 immutable-source review
2026-09-13. Reviewer: Codex-956atf `/root/source8_syntax_review`, GPT-6 Astra. Parent owns synthesis, integration, compiler leases and Gitea. Review pin: `02b9d5ce4c73d0446146b13b46a228e6fd4d7735`, parent `112e0d910bd2b2c9a191f29536fecb0fbc0177b0`. All implementation observations below come from `git show` of that pin, not a mutable working-tree copy. The six-file diff contains 1,345 insertions.
**Outcome: no blocking S1 source defect found in this bounded review.** This is source-review evidence only: no compilation, tests, timing, memory measurement, native UI, source edits or Gitea writes. It neither establishes executable correctness nor approves public source8 exposure. Read root AGENTS.md, the r2 design and the S0/S1 handoff. Existing semantic types and exact-literal implementation were checked at the same pin. Shared coordination remains the parent's responsibility under the delegated review scope.
## Examined invariants
- `syntax.rs:159–204,531–550`: Pratt continuation uses precedence+1 for the right operand, preserving left association and right-grouped trees. Comparison rejection checks both children; grouping annotates the particular completed node and does not propagate to a newly constructed parent. Source inspection distinguishes `1 - (2 - 3)` from `1 - 2 - 3`, `(1 < 2) == true` from `1 < 2 == true`, and `true == (1 < 2)` from `true == 1 < 2`. The twelve operator mappings match the existing BinaryOp variants.
- `syntax.rs:62–156,285–336,433–508`; `analysis_source.rs:46–88`: all thirteen Expr shapes are represented without numeric folding or conversion through JSON/f64. Prefix minus creates Unary; a negative ratio produces Number. Quantity suffixes are consumed only by the literal path, so `(1 + 2)[@unit]` cannot silently become a quantity with an expression magnitude. Sum requires its initial expression; constructor arity, trailing commas and signed integer Power bounds reject locally. Alias and parameter resolution is deliberately absent.
- `lex.rs:16–138`; `syntax.rs:388–432`: a decimal/scientific atom comes from one contiguous raw numeric token. `1e-2` remains one token while `1-2` separates; malformed adjacency such as `1_0`, `0x1`, `1.2.3` rejects. Ratio components require integer digit tokens with an optional numerator minus, and bounded component lengths precede reconstruction. The reconstruction length constant correctly accounts for `ratio(`, comma+space and closing parenthesis. Original full literal and component/punctuation spans survive comments; only numeric spelling reaches the shared Exact meter.
- `syntax.rs:227–268,367–386`: call argument names are checked for duplicate insertion before value parsing. Attempted entries and references are charged before name allocation. Repeated call sites retain separate callee/key spans because spans come from each occurrence rather than a callee/name lookup. This is enough syntax information for subsequent owner/path qualification; it does not itself implement that qualification or rename transactions.
- `budget.rs:71–136`; `lex.rs:17–32`: byte/token/entry/reference counters share one explicit Budget across parse calls, reserve before relevant allocation, and cap arithmetic through subtraction before addition. Resource exhaustion latches; numeric admission uses one WorkMeter. Comments consume raw bytes and lexical trivia tokens. No per-literal reset or counter-overflow route was found through the intended methods.
- `syntax.rs:48–337,443–479`: parser descent is an explicit task/value machine. Frames bound nested syntax; completed Expression children are checked before an accepted parent can exceed logical depth128. A failing proposed parent may own depth128 children, so its teardown has at most the one additional rejected parent layer. Flat binary trees cannot grow accepted depth without that check, and unfinished Sequence/Task containers hold bounded accepted child trees rather than a recursively nested task chain. This structural argument does not replace adverse-input execution and cleanup/RSS measurement.
- `language.rs:4–7`: the only shared-language delta is the cfg(test) module hook. Existing public header rejection and generic tokenization implementations are unchanged in the reviewed diff. The test-staged raw lexer is not yet a tolerant public tokenizer, declaration grammar or public source8 reader; those gaps are acknowledged S2/S3/integration work, not regressions introduced by this pin.
## Nonblocking verification improvements before grammar exposure
1. **Assert mixed-precedence topology, not only parse success.** At `tests.rs:157–169`, `1 + 2 * 3` and `not(false) and true or false` only have successful parsing checked. Those assertions would also pass with several incorrect precedence tables. Add independently matched trees for those inputs and `-1 * 2`, `1 / 2 * 3`, `1 + (2 + 3)`; check actual operators and child placement. The current implementation looks correct by inspection, so this is an oracle-strengthening recommendation rather than a reproduced grammar defect.
2. **Add a flat-binary depth boundary and a deep completed-child failure case.** `tests.rs:306–390` covers unary128/129, redundant groups256/257 and truncated wide syntax. Add 128 Boolean leaves joined by `and` (depth128 success), 129 leaves (logical rejection), and a constructor holding a completed depth128 subtree followed by a malformed second argument. These exercise Binary task ownership and teardown of completed deep children, which the existing short-prefix truncation test does not directly cover. Run them under the parent's executable/measurement gate; no stack or performance result is inferred here.
## Retained acceptance boundaries
The nine authored tests remain unrun in this review. Full declaration/type parsing, short-word registration, qualified scoped resolution, canonical AST-preserving writing, source indexes/edits, full-bundle aggregate charging, shared strict/tolerant tokenizer composition, old-version migration and guarded save remain outside this S1 pin. The r2 public whole-Project and literal-trivia update oracles remain necessary. Profile ceilings remain candidates pending measured adoption. No conclusion is offered on JSON depth enablement, Linux, evaluator behavior or native/API parity.
@@ -0,0 +1,18 @@
[
{
"name": "analysis-source8-repair-design-r2-20260913.md",
"sha256": "3a5b96fd130e3859932ee25544b3ff2f6a21f0774f3b5849e546f9574b9dbf7d"
},
{
"name": "analysis-source-save-guard-independent-review-20260913.md",
"sha256": "4222a566c3759201a8844e73e36c39bbea5faa39fbfbf382c4be69143c303d79"
},
{
"name": "analysis-source8-s1-independent-review-20260913.md",
"sha256": "08eefcb8870fb3e2f68b04f79d2c095171a976bdeb52c555e19095c840603662"
},
{
"name": "analysis-source8-s0-s1-handoff-20260913.md",
"sha256": "ea8b525daf0106c99ea5460aa64ce0c7a691e3dc583b93f4ac6642f0956c119e"
}
]