From 3cf345b146afd1139709df9293148b915f1650f7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 09:16:20 +0000 Subject: [PATCH 1/3] fix(compile): reject blank/whitespace-only slot deep and scan entries validateConfig() checked slot.deep truthiness and slot.scan array length, but never checked that individual scan entries (or deep) are non-blank after trimming. A config with scan: ['', 'tests'] or deep: ' ' passed validation cleanly and compile() then silently emitted a corrupted line (SCAN=,tests / DEEP= ). Same defect class as the cron-minute-field (#24), adrConvention-shape (#28/#29) and bonusModuli-value (#78/#79) compiler-parity fixes, scoped to the one remaining gap in the same validator. npm test: 98->101 (+3), 0 regressions. Self-hosted dream.config.json compile output byte-identical before/after. Dream Cycle 2026-09-10. DEEP=compiler-parity. Issue #104. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01QXtKpy19GfqPGwpPG9iAk6 --- .../2026-09-10-compiler-parity-report.md | 105 ++++++++++++++++++ packages/compile/src/config.ts | 10 +- packages/compile/src/index.test.ts | 19 ++++ 3 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 docs/dream-cycle/2026-09-10-compiler-parity-report.md diff --git a/docs/dream-cycle/2026-09-10-compiler-parity-report.md b/docs/dream-cycle/2026-09-10-compiler-parity-report.md new file mode 100644 index 0000000..2f58ea6 --- /dev/null +++ b/docs/dream-cycle/2026-09-10-compiler-parity-report.md @@ -0,0 +1,105 @@ +# Compiler-Parity SOTA Report — 2026 + +## TL;DR + +`validateConfig()` in `@dream-machine/compile` checks that a rotation slot's +`scan` array has `length >= 1` and that `deep` is truthy, but never checks +that individual `scan` entries (or `deep`) are non-blank strings. A +config with an empty-string or whitespace-only slot surface passes +validation cleanly and then silently compiles a malformed nightly-routine +prompt: `SCAN=,tests` (dangling leading comma, blank surface) or +`DEEP= ` (whitespace deep-dive name with no visible content). This is the +same defect class as three prior ACCEPTed compiler-parity nights — the cron +minute field (PR #24, merged), `adrConvention` shape (issue #28/PR #29, +open), and `bonusModuli` values (issue #78/PR #79, open) — extended to the +one remaining unchecked field group in the same validator. + +## What's new + +Nothing external — this is an internal-only, self-hosting finding scoped to +this repo's own `@dream-machine/compile` package, discovered by systematically +walking every field `validateConfig` does *not* check against every field the +compiled template actually interpolates unescaped. + +## Competitors (how comparable systems guard config→prompt compilation) + +| System | Guard against blank/degenerate structured-config fields | Grade | +|---|---|---| +| Sakana AI Scientist | Config loaded via Hydra/OmegaConf; relies on YAML schema + Python type hints, no explicit blank-string rejection in the loop-config path (community reports of silent empty-list bugs in structured configs) | C (community reports, not audited firsthand) | +| OpenHands | Its `config.toml` loader validates types via Pydantic, which by default treats an empty `str` as a *valid* string unless `min_length=1` is set per field | B (public docs/source, cross-checked against pydantic defaults) | +| DSPy/GEPA | GEPA's mutation/config surface is typically Python objects, not a serialized schema boundary, so this exact class (JSON config → templated prompt with unchecked blank fields) doesn't directly apply | C (inference from public repo structure) | +| SWE-agent | YAML task configs are loaded via a schema class; empty-string fields in list-typed config keys are not rejected by default in observed configs | C (single-source, not independently reproduced) | +| AutoGPT lineage | JSON/YAML agent configs historically accepted blank list entries silently (well-known class of "empty step" bugs in early AutoGPT forks) | C (community/single-source) | + +No competitor evidence is graded A — this finding is validated entirely by +first-hand reproduction against this repo's own code (see Evaluation below), +which is what the ACCEPT verdict below rests on, not the competitor table. + +## Hypothesis (frozen before implementation) + +> Given a `dream.config` whose `slots[i].scan` array contains an empty-string +> or whitespace-only entry, or whose `slots[i].deep` is a whitespace-only +> string, when `validateConfig()` is extended to reject blank surface names +> in both fields (mirroring the existing non-empty check pattern already +> used for `slot.deep`'s truthiness and `bonusModuli` keys), then +> `validateConfig` should report `ok: false` with a specific per-slot error +> for such configs, and `compile()` should never again be reachable with a +> blank `SCAN=` segment or blank `DEEP=` line for any config that passes +> validation — subject to: 0 regressions on the existing 98 tests, no change +> to any currently-valid config's validation result or compiled output +> (self-hosted `dream.config.json` and the `metaharness` fixture config both +> byte-identical before/after). + +## Benchmarks / Evaluation + +Real evaluator: `npm test` (vitest, 98 tests on baseline `HEAD=7933c35`). +Live pre-fix reproduction (grade A, first-hand, this session): + +``` +$ node -e "... cfg.slots[0].scan = ['', 'tests']; validateConfig(cfg) ..." +validation: {"ok":true,"errors":[],"warnings":[]} +compiled SCAN line -> " SCAN=,tests" + +$ node -e "... cfg.slots[1].deep = ' '; validateConfig(cfg) ..." +validation (whitespace deep): {"ok":true,"errors":[],"warnings":[]} +compiled DEEP line -> "1: DEEP= " +``` + +Both are config states no repo maintainer would author on purpose, but +nothing in the schema, the CLI, or CI rejects them — and if `dream.config.json` +for any Dream-Machine-managed repo (this one included) is hand-edited and a +scan/deep field accidentally lands blank (e.g. a bad find/replace, a JSON +array reformat, a merge conflict resolved wrong), the nightly routine +silently degrades instead of failing loud. + +## Witness + +See STEP 16 below (this file's own witness section, rewritten post-stamp). + +## Next steps + +1. Extend `validateConfig`'s existing per-slot loop to reject blank + (`''`/whitespace-only) `scan` entries and tighten `deep`'s truthiness + check to also reject whitespace-only strings — one conceptual change, + reusing the established per-field-blank-check pattern. +2. Add regression tests mirroring the existing `validateConfig` test block's + style (`rejects a non-integer bonus modulus key` neighbor). +3. Do **not** duplicate the two already-open, already-ACCEPTed sibling + findings in this same file (issue #28/PR #29 `adrConvention` shape, + issue #78/PR #79 `bonusModuli` values) — both remain open for human + review; this candidate is scoped to the one remaining gap in the same + validator, not a re-submission of either. + +--- + +## Witness + +``` +report_sha256 : 196cd963b339a38e4cb2bffd697ef2967c1e931fc7a8f670e980066c4290e37b +session_commit: 7933c3599abe22df5290f4609d1f93f598feb3de +witness : 2eae81eb6c138360f60e395e06d33cce59cc4cfe5f511c91f4878601505d3036 +``` + +Verify (5 steps, coreutils only): take this file's content up to (not including) +this `## Witness` section's leading `---`, `sha256sum` it — must equal +`report_sha256` above. Then `printf '%s%s' "" "7933c3599abe22df5290f4609d1f93f598feb3de" | sha256sum` — must equal `witness` above. Reproduced live this session via `node packages/cli/dist/bin.js witness stamp 7933c3599abe22df5290f4609d1f93f598feb3de`. diff --git a/packages/compile/src/config.ts b/packages/compile/src/config.ts index cbb8e4c..d553227 100644 --- a/packages/compile/src/config.ts +++ b/packages/compile/src/config.ts @@ -90,8 +90,14 @@ export function validateConfig(config: Partial): ValidationResult { errors.push('at least one rotation slot is required'); } else { config.slots.forEach((s, i) => { - if (!s.deep) errors.push(`slot ${i}: missing "deep" surface`); - if (!s.scan || s.scan.length < 1) warnings.push(`slot ${i}: no scan surfaces`); + if (!s.deep || !s.deep.trim()) errors.push(`slot ${i}: missing "deep" surface`); + if (!s.scan || s.scan.length < 1) { + warnings.push(`slot ${i}: no scan surfaces`); + } else { + s.scan.forEach((sc, j) => { + if (!sc || !sc.trim()) errors.push(`slot ${i}: scan[${j}] must be a non-empty surface name`); + }); + } }); } if (config.bonusModuli) { diff --git a/packages/compile/src/index.test.ts b/packages/compile/src/index.test.ts index 43d24d2..9ee4efc 100644 --- a/packages/compile/src/index.test.ts +++ b/packages/compile/src/index.test.ts @@ -47,6 +47,25 @@ describe('validateConfig', () => { it('rejects a non-integer bonus modulus key', () => { expect(validateConfig({ ...metaharness, bonusModuli: { x: 'y' } }).ok).toBe(false); }); + it('rejects a blank or whitespace-only deep surface', () => { + for (const deep of ['', ' ']) { + const slots = [{ ...metaharness.slots[0], deep }, ...metaharness.slots.slice(1)]; + const r = validateConfig({ ...metaharness, slots }); + expect(r.ok).toBe(false); + expect(r.errors.join()).toMatch(/missing "deep" surface/); + } + }); + it('rejects a blank or whitespace-only scan entry', () => { + for (const scan of [['', 'turn-credit'], ['router', ' ']]) { + const slots = [{ ...metaharness.slots[0], scan }, ...metaharness.slots.slice(1)]; + const r = validateConfig({ ...metaharness, slots }); + expect(r.ok).toBe(false); + expect(r.errors.join()).toMatch(/scan\[\d+\] must be a non-empty surface name/); + } + }); + it('does not flag a well-formed scan array', () => { + expect(validateConfig(metaharness).warnings).toHaveLength(0); + }); }); describe('compile', () => { From 106a8046cdb0bfc7eef38ee35228cf4d1fff27d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 09:17:22 +0000 Subject: [PATCH 2/3] docs(dream): append 2026-09-10 compiler-parity ledger row References issue #104 and PR #105 (both now known after PR creation). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01QXtKpy19GfqPGwpPG9iAk6 --- docs/dream-cycle/LEDGER.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dream-cycle/LEDGER.md b/docs/dream-cycle/LEDGER.md index e2cdc37..ffb8f9b 100644 --- a/docs/dream-cycle/LEDGER.md +++ b/docs/dream-cycle/LEDGER.md @@ -6,3 +6,4 @@ | 2026-08-24 | portfolio 310; one private infrastructure aggregate; metaharness; open-claude-code; rvm; rufield | coordinate federation evidence gates; reuse execution-control and RVF findings; record RVM CI provenance debt and RuField BLE contract evidence | reuse open-claude-code#17, metaharness#22/#172/#222; rvm#52 | rufield#5; dream-machine#24 | partial | ACCEPT / INCONCLUSIVE | 58 recent commits across 6 repositories; RVM ruv:// parse 17.6-24.5% faster with 1280 tests green; RuField software contract CI green; private details redacted | RVM 580c006b; RuField 80577749; MetaHarness 44fbcdd6 | #24 stays draft and green; openAVO#1 and RuVector#908 remain open; no session merge or self-promotion | | 2026-08-25 | portfolio 310; ruflo; RuVector; worldgraph; RuView; rvcsi | isolate Ruflo install gate; correct RuVector timeout attribution; record WorldGraph package/MCP breakage; validate sensor software-chain contracts | ruflo#3095; worldgraph#3; RuVector#825/#928 | reuse ruflo#3094, RuView#1696, rvcsi#3; dream-machine#24 | partial | ACCEPT / REJECT / INCONCLUSIVE | 9 public default-branch commits across 4 of 8 changed public repos; RuView 71/71 observed checks green and rvCSI 4/4 green; Ruflo install-dependent gates red; no new critical/high security finding | Ruflo a86ad56c; RuView 87ce7bdd; rvCSI 499b6873 | RuField#5 merged by maintainer; #24 stays draft/unmerged; tracked issues remain open; private activity retained only as aggregate; no federation claim | | 2026-08-26 | portfolio 311; rufield; batvu; open-claude-code; LatentMesh; metaharness | retain one newly merged sensor-replay trust finding for private advisory; reject BatVu frozen install, Open Claude execution boundary and MetaHarness stale installer; accept LatentMesh governed simulation while rejecting its persistence label | reuse open-claude-code#17, metaharness#222 | review batvu#8, LatentMesh#8, open-claude-code#24; dream-machine#24 | partial | ACCEPT / REJECT | 27 default-branch commits across 5 public repos; LatentMesh simulated Darwin gate reports 74.2% compute-proxy reduction with task success preserved; BatVu CI stops at npm ci; private activity 0 repos/0 commits | RuField 99556728; BatVu 1302ec02; LatentMesh 4214d51d | #24 stayed draft/green before ledger update; Ruflo#3095, WorldGraph#3 and RuVector#928 remain open; no public disclosure, new implementation PR, direct push, merge, or federation claim | +| 2026-09-10 | compiler-parity | validateConfig() checked slot.deep truthiness and slot.scan array length but never rejected blank/whitespace-only individual scan entries or a whitespace-only deep field, silently compiling corrupted SCAN=/DEEP= lines; added per-entry blank-string checks (4th compiler-parity night on this validator, scoped to the one gap not already covered by open PRs #29/#79) | #104 | #105 | yes | ACCEPT | npm test 98->101 (+3), 0 regressions; self-hosted dream.config.json compile output byte-identical | 196cd963b339a38e4cb2bffd697ef2967c1e931fc7a8f670e980066c4290e37b / 2eae81eb6c138360f60e395e06d33cce59cc4cfe5f511c91f4878601505d3036 | main's LEDGER.md remains stale (7 rows incl. tonight vs. ~40 accumulated across unmerged dream/* branches, PR #89's own fix for this gap still unmerged); PR #24 (cron floor) confirmed MERGED via pull_request_read; #29 (adrConvention), #79 (bonusModuli values) confirmed OPEN/draft via pull_request_read, not duplicated tonight; no session merge or self-promotion | From 2be17af5dfe4ed5e7c1004eee9dcc18f5e725535 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 09:19:43 +0000 Subject: [PATCH 3/3] fix(compile): guard slots[].scan against non-array (bare string) values Review feedback on this PR (from a concurrent 2026-09-15 compiler-parity session, issue #110/PR #111): a config with scan: "config-schema" (a bare string instead of an array) passed the s.scan.length < 1 check (strings have .length) and then crashed validateConfig() itself with TypeError: s.scan.forEach is not a function, inside the blank-entry loop this PR just added. Same underlying type-confusion class as the pre-existing crash this PR's reviewer reproduced on main @ 3edd426 (TypeError: s.scan.join is not a function inside compile()) -- this PR's fix just moved the crash site into validateConfig without guarding it. Adds an explicit Array.isArray(s.scan) check ahead of the blank-entry loop, reported as a normal validation error instead of a thrown exception. Missing scan (undefined) keeps its prior warning-only behavior unchanged; only the "present but wrong type" case is new. npm test: 700->702 (+2), 0 regressions. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01QXtKpy19GfqPGwpPG9iAk6 --- packages/compile/src/config.ts | 6 +++++- packages/compile/src/index.test.ts | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/compile/src/config.ts b/packages/compile/src/config.ts index 3fa996b..cd93713 100644 --- a/packages/compile/src/config.ts +++ b/packages/compile/src/config.ts @@ -91,7 +91,11 @@ export function validateConfig(config: Partial): ValidationResult { } else { config.slots.forEach((s, i) => { if (!s.deep || !s.deep.trim()) errors.push(`slot ${i}: missing "deep" surface`); - if (!s.scan || s.scan.length < 1) { + if (!s.scan) { + warnings.push(`slot ${i}: no scan surfaces`); + } else if (!Array.isArray(s.scan)) { + errors.push(`slot ${i}: "scan" must be an array of surface names`); + } else if (s.scan.length < 1) { warnings.push(`slot ${i}: no scan surfaces`); } else { s.scan.forEach((sc, j) => { diff --git a/packages/compile/src/index.test.ts b/packages/compile/src/index.test.ts index 52146aa..77e1fd0 100644 --- a/packages/compile/src/index.test.ts +++ b/packages/compile/src/index.test.ts @@ -68,6 +68,22 @@ describe('validateConfig', () => { it('does not flag a well-formed scan array', () => { expect(validateConfig(metaharness).warnings).toHaveLength(0); }); + it('rejects a bare-string scan instead of an array, without throwing', () => { + const slots = [{ ...metaharness.slots[0], scan: 'config-schema' }, ...metaharness.slots.slice(1)] as typeof metaharness.slots; + let r; + expect(() => { + r = validateConfig({ ...metaharness, slots }); + }).not.toThrow(); + expect(r!.ok).toBe(false); + expect(r!.errors.join()).toMatch(/slot 0: "scan" must be an array of surface names/); + }); + it('still warns (not errors) when scan is entirely absent', () => { + const { scan: _scan, ...slot0 } = metaharness.slots[0]; + const slots = [slot0, ...metaharness.slots.slice(1)] as typeof metaharness.slots; + const r = validateConfig({ ...metaharness, slots }); + expect(r.ok).toBe(true); + expect(r.warnings.join()).toMatch(/no scan surfaces/); + }); it('accepts a well-formed bonus modulus value', () => { expect(validateConfig({ ...metaharness, bonusModuli: { '25': 'vertical-packs' } }).ok).toBe(true); });