Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
input cells). **A cluster member is never size-skipped** (`MAX_SHEET_SIZE_MB` silently
dropped the monster sheets from the cluster → partial-cluster wrong fixed point; regression
red pre-fix with `clustersTotal=0`). New `EVAL_CLUSTER_TIMEOUT_MS` (default 60min).
- **per-sheet-eval dynamic-read scan knows the #66 helpers** — the v0.3.1 emitter lowers
`ref:OFFSET(...)` through `_dynRange`/`_offsetAddr` with no bare `_offset(` call, so the
GT-seed-scoping scan approved scoping on exactly the builds where ranges are runtime-
addressed (observed live on the a1-66c canonical eval). Markers added (red pre-fix in the
new MODEL C). Defense-in-depth today: `_dynRange` anchors are same-sheet-only because a
sheet-qualified computed-endpoint range refuses to parse (honest NaN) — filed **#71**.
- **#46 row-chunked sheet modules** (`--max-module-mb=N`, default 64, 0 disables): any sheet
module crossing the cap rotates into `<Sheet>.partNNN.mjs` modules behind a same-named
facade — ONE logical `compute()`, identical write sequence, statement-boundary splits only,
Expand Down
8 changes: 7 additions & 1 deletion eval/per-sheet-eval.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,13 @@ async function main() {
// so the scan must follow the facade's part imports or it would silently
// approve scoping for exactly the monster sheets most likely to use OFFSET.
let _dynamicRead = false;
const _hasDynamicRead = (src) => src.includes('_offset(') || src.includes('ctx.get(String(');
// _dynRange/_offsetAddr are the #66 computed-endpoint-range helpers — the
// v0.3.1 emitter lowers `ref:OFFSET(...)` through them WITHOUT any bare
// `_offset(` call, so a scan that only knows the scalar markers silently
// approves scoping for exactly the builds where ranges are runtime-addressed
// (observed live on the a1-66c canonical eval).
const _hasDynamicRead = (src) => src.includes('_offset(') || src.includes('_dynRange(')
|| src.includes('_offsetAddr(') || src.includes('ctx.get(String(');
for (const m of ct.members) {
try {
const src = await readFile(m.modulePath, 'utf8');
Expand Down
41 changes: 41 additions & 0 deletions pipelines/rust/tests/test-row-chunked-modules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,47 @@ console.log('Testing: row-chunked module emission (#46) — facade + parts, one
}
}

// ── MODEL C: the ONLY dynamic construct is a computed-endpoint range (_dynRange) ──
{
// The v0.3.1 emitter (#66) lowers `ref:OFFSET(...)` through _dynRange/_offsetAddr
// with NO bare `_offset(` call — observed live on the a1-66c canonical eval, where
// the scan found no `_offset(` and approved GT-seed scoping. _dynRange anchors are
// currently same-sheet only (a sheet-qualified anchor refuses to parse -> honest
// NaN), so cluster-scope warm-starts cover its reads today and this is
// defense-in-depth — but the scan must still refuse to scope what it cannot
// statically bound. No row-chunking needed: the scan reads plain modules too.
const Loop1 = {
'!ref': 'A1:C3',
A1: n(2, '0.5*Loop2!A1+1'),
B1: n(6, 'SUM(C1:OFFSET(C1,2,0))+0*Loop2!A1'),
C1: n(1), C2: n(2), C3: n(3),
};
const Loop2 = { '!ref': 'A1:A1', A1: n(2, '0.5*Loop1!A1+1') };

const { tmp, chunked } = build({ Loop1, Loop2 }, ['Loop1', 'Loop2'], CAP_MB);
try {
const src = readFileSync(join(chunked, 'sheets', 'Loop1.mjs'), 'utf-8');
assert(src.includes('_dynRange(') && !src.includes('_offset('),
'fixture emits _dynRange with no bare _offset( (else this model discriminates nothing)');

const EVAL = join(ROOT, 'eval', 'per-sheet-eval.mjs');
const out = join(tmp, 'report.json');
let stdout = '';
try {
stdout = execFileSync('node', [EVAL, chunked, '--output', out, '--sample', '50000'],
{ encoding: 'utf-8', stdio: 'pipe', maxBuffer: 64 * 1024 * 1024 });
} catch (e) { stdout = String(e.stdout || ''); }
const report = existsSync(out) ? JSON.parse(readFileSync(out, 'utf-8')) : null;

assert(/keeping FULL GT seed/.test(stdout),
'per-sheet-eval flags _dynRange as a runtime-addressed read and refuses to scope');
assert(report !== null && report.summary.clustersConverged === 1 && report.summary.overallAccuracy === 100,
`cluster converged at 100% (got ${report && report.summary.clustersConverged}, ${report && report.summary.overallAccuracy}%)`);
} finally {
rmSync(tmp, { recursive: true, force: true });
}
}

console.log('');
console.log(`Results: ${passed} passed, ${failed} failed, ${passed + failed} total`);
process.exit(failed > 0 ? 1 : 0);
Loading