Skip to content

[Dream Cycle 2026-08-29] developer-experience: ledger append accepts any --verdict/--evaluated value, corrupting ledger schema + cli,tui scan #48

Description

@ruvnet

Rotation

DAYINT % 5 = 4 → DEEP=developer-experience, SCAN=cli,tui. No bonus deep dive (DAYINT % 25 = 4, DAYINT % 75 = 29). Session commit 7933c359.

Ledger Check

docs/dream-cycle/LEDGER.md (main) has 6 rows: 1 real (2026-08-13, PR #7, merged) + 5 structurally invalid rows (2026-08-22 → 2026-08-26) that fail dream-machine ledger verify with 9 errors. Root-caused via git log -- docs/dream-cycle/LEDGER.md: all 5 landed in one commit, df9ff40 (fix(compile): enforce documented hourly cron floor (#24), 2026-08-26), which bulk-added them alongside an unrelated cron fix — a direct hand-edit to LEDGER.md that bypassed ledger append entirely, violating this repo's own "exactly one row per run" invariant (ADR-0001, compiled STEP 25, @dream-machine/ledger's doc comment).

gh CLI unavailable this session; GitHub state checked via mcp__github__* instead (not FALLBACK). Confirmed: this repo's self-hosting dream-cycle has run one PR/issue per slot-rotation night since 2026-08-14 (#8/#9#45/#46), matching dream.config.json's rotation exactly, and zero of those PRs are merged (merged: false on all, via list_pull_requests) — real ground truth for zeroMergeStreak, not a wiring gap (PR #27 already fixed that gap on 2026-08-24). A separate portfolio-wide automation (draft PR #47, same base commit as this session) also ran today against many other repositories; its ledger rows are the same malformed ones already on main.

Learning Signals

dream-machine ledger signals: {"zeroMergeStreak": true, "duplicateDirections": [], "lowScoreStreak": false, "blockedEvalStreak": false, "nightsConsidered": 6}. Per STEP 1.1, zero-merge biases tonight toward a tiny, one-parameter, easily-reviewable candidate — this finding fits.

Deep Dive

ledger append's --verdict/--evaluated flags are written to LEDGER.md with no validation, even though ledger verify enforces a strict enum (ACCEPT|REJECT|INCONCLUSIVE / yes|no|blocked) on those exact columns (packages/ledger/src/index.ts, verifyLedger). The two functions never share validation. Fix: packages/cli/src/index.ts's append handler now imports the same VERDICTS/EVALS constants verifyLedger uses (newly exported from @dream-machine/ledger) and rejects (exit 1, no write) before calling appendRow.

Hypothesis (frozen before implementation)

Given dream-machine ledger append, when it validates --verdict against ACCEPT|REJECT|INCONCLUSIVE and --evaluated against yes|no|blocked (the same enums ledger verify already enforces) and exits non-zero without writing on violation, then an out-of-schema ledger append invocation should be rejected before it reaches LEDGER.md, relative to the baseline (any string silently accepted and written) — subject to: existing valid invocations keep working unchanged, error/exit-code convention matches ledger verify, no other subcommand changes, full existing suite (98 tests) stays green.

Evaluation Receipt

Real evaluator: npx vitest run (repo's bench entrypoint, npm test). Corpus: packages/cli/src/index.test.ts's existing ledger describe block + 2 new cases.

Baseline (parent 7933c359, tests added, fix NOT applied) Candidate (fix applied)
New tests 2 failing 2 passing
Full suite 98 passing, 2 failing (100 total) 100/100 passing
Lint clean clean
Build (tsc per package) clean clean

Live manual replay against a scratch copy of the real docs/dream-cycle/LEDGER.md (not just mocked tests): bad --verdict and bad --evaluated both exit 1 with the file's sha256 unchanged; a valid append still succeeds. Diff: 3 files, 32 lines (packages/cli/src/index.ts +10, packages/ledger/src/index.ts +2/-2 export-only, packages/cli/src/index.test.ts +20 tests).

Darwin Results

Not run. DARWIN=unavailablenpx --no-install @metaharness/darwin confirms the optional peer package isn't installed in this checkout (npm ci only installs workspace packages). Not faked.

Evidence

  • OBSERVATION: ledger verify --path docs/dream-cycle/LEDGER.md → 9 errors, rows 2–6.
  • OBSERVATION (grade A): git show df9ff40 -- docs/dream-cycle/LEDGER.md → single commit bulk-added all 5 malformed rows alongside an unrelated cron fix.
  • MEASUREMENT: baseline 98 passing/2 failing → candidate 100/100, via npx vitest run.
  • MEASUREMENT: manual CLI replay against a scratch ledger copy — hash-unchanged rejection, working valid append.
  • INFERENCE: the append/verify validation split is consistent with how CLI-driven drift could occur, but is not the mechanism that produced the 5 real malformed rows (those came from a direct file edit, not ledger append) — recorded honestly, not overclaimed.
  • DECISION: ship the CLI validation; do not attempt to repair the 5 historical rows (human decision) or add a CI ledger verify gate yet (would turn CI red today against the real, currently-invalid main ledger — sequencing issue, see Recommendation).

Reward-Hack Check

Independent critic pass (self-review, single session — no second agent spawned tonight; recorded as a real limitation, not glossed over, per this repo's own precedent nights). Weakened benchmark? No — additive tests only. Altered gold/thresholds? None exist for this surface. Cherry-picked corpus? No — existing test file, unmodified except the 2 additions. Exploited the evaluator? No — vitest run invoked exactly as CI does. Hidden cost/undocumented cache? No — O(1) membership checks against 2 constants already defined upstream, imported not duplicated. Verdict: clear.

Security Review

Additive input validation on a local CLI flag; reduces attack surface (a malformed --verdict/--evaluated can no longer land in a file ledger signals/tui parse and trust downstream). No new filesystem/network scope, no credential handling, no change to witness/verify-entrypoint. Not security-sensitive enough to warrant its own ADR.

Scan Findings

cli — the deep dive itself (above): ledger append had no verdict/evaluated validation.

tuirenderDashboard (packages/cli/src/tui.ts) never calls verifyLedger, so dream-machine tui shows zero visual signal that 9 rows in the real LEDGER.md are structurally invalid. Not fixed tonight (second conceptual change: surfacing verify errors inside the fixed-width dashboard layout) — candidate for a future developer-experience/tui night.

Competitors

Grade C (general knowledge, framing only — not the justification for this candidate, which rests on first-hand internal evidence). SWE-agent, OpenHands, DSPy/GEPA, and Sakana AI Scientist all persist their durable run-log/state via typed objects constructed by the harness itself (pydantic models, dataclasses, structured JSON), not free-text CLI flags composed into an untyped file — so the write path and the read/verify path share one validated type by construction. The AutoGPT lineage is the closest analog to tonight's bug: historically, free-text memory/log writers drifting out of the schema downstream code assumed was a known root cause of brittleness. @dream-machine/ledger had verifyLedger() and appendRow() as two independent, unvalidated-against-each-other functions — the concrete in-repo instance of that anti-pattern, now closed for the CLI write path.

Gist

No gh gist create and no gist-creation MCP tool available this session (gh CLI absent, matching repo precedent, e.g. 2026-08-14 and 2026-08-24 nights). GIST=LOCAL — report committed at docs/dream-cycle/2026-08-29-developer-experience-report.md.

Witness

report_sha256 : cdcd1934830101f0c139a4a3f974ced0dc193e65c52db2c6774efed913fd59c2
session_commit: 7933c3599abe22df5290f4609d1f93f598feb3de
witness       : b1e4ed603fb970845d4c4b0eda8c6e2f624820a59531c24d2b26965bafdf059f

Reproduce: sha256sum docs/dream-cycle/2026-08-29-developer-experience-report.md then printf '%s%s' "$REPORT_HASH" "7933c3599abe22df5290f4609d1f93f598feb3de" | sha256sum — must equal the witness above. Equivalently: node packages/cli/dist/bin.js witness stamp docs/dream-cycle/2026-08-29-developer-experience-report.md 7933c3599abe22df5290f4609d1f93f598feb3de.

Recommendation

evaluated: accepted for human review of the draft PR (small, deterministic, no LLM calls, 100/100 tests green, 32-line diff). Not attempted tonight, explicitly:

  1. Repair or accept-as-historical-wart the 5 malformed ledger rows from commit df9ff40 — human decision.
  2. A ledger verify CI gate (would catch a future df9ff40-shaped commit) — deliberately not added yet; it would fail today against main's real, currently-invalid ledger. Do (1) first.
  3. Surface ledger verify errors inside dream-machine tui (Scan Findings, tui).
  4. Re-run this candidate through bounded Darwin once @metaharness/darwin is available.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions