Skip to content

Fix silent ledger death + field gaps (3.9.14) - #13

Merged
suboss87 merged 1 commit into
Mainfrom
fix/silent-ledger-and-field-gaps
Jul 21, 2026
Merged

Fix silent ledger death + field gaps (3.9.14)#13
suboss87 merged 1 commit into
Mainfrom
fix/silent-ledger-and-field-gaps

Conversation

@suboss87

Copy link
Copy Markdown
Owner

Summary

  • Doctor detects broken .fde/.git (not just missing) and shouts UNVERSIONED + repair hint
  • Garden stops claiming reversibility when ledger is broken; refuses --apply
  • Receipts marks ON RECORD hits in dirty agreement files
  • Smart debrief caps preview lines; Decided: → decisions.md
  • Garden proposes/applies duplicate open-risk consolidation

Test plan

  • npm run check (65 tests; regressions for all four ground-test findings)
  • Squash-merge → tag v3.9.14npm publish
  • npm view fdeops version3.9.14

Made with Cursor

Make corrupt memory git a loud doctor/garden failure, caveat dirty receipts, cap smart-debrief previews, route Decided: to decisions, and garden-dedupe open risk echoes.

Co-authored-by: Cursor <cursoragent@cursor.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment thread bin/fde.js
Comment on lines +1888 to 1890
const hash = commitMemory(eng, 'garden', { files: [...touched] })
if (!applied) console.log('no mechanical proposals applied (manual items remain)')
else console.log(`garden done${hash ? ` @${hash}` : ''}`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Memory tidy-up can silently commit unrelated hand-edits when nothing is tidied

When the memory tidy-up runs with confirmation but no cleanup actually changes any file, every uncommitted manual edit to the records is swept into the automatic commit (commitMemory(eng, 'garden', { files: [...touched] }) at bin/fde.js:1888), so hand-edits get laundered into the trusted history.
Impact: Hand-edited records that were meant to stay flagged as untrusted get quietly baked into the tamper-evident memory under an automated label, defeating the whole point of the ledger.

Empty files array falls through to git add -A

When touched is empty (e.g. fde garden --apply where the only proposal is the manual reconfirm-signal, or where applyRiskDedupe matched nothing and returned 0), commitMemory(eng, 'garden', { files: [] }) is invoked. In bin/lib/memory.js:40-56, opts.files is [], so files is []; the guard if (files && files.length) is false because [].length === 0, and execution falls into the else branch that runs git add -A. This stages ALL dirty paths — including foreign manual edits — and commits them under the message garden. The pre-existing code always passed ['context.md', 'context-archive.md'] (a non-empty list), so it never triggered git add -A; the new [...touched] can be empty, reintroducing the tamper-laundering the scoped-commit design was built to prevent (see comment at bin/lib/memory.js:30-33).

Suggested change
const hash = commitMemory(eng, 'garden', { files: [...touched] })
if (!applied) console.log('no mechanical proposals applied (manual items remain)')
else console.log(`garden done${hash ? ` @${hash}` : ''}`)
const hash = touched.size ? commitMemory(eng, 'garden', { files: [...touched] }) : null
if (!applied) console.log('no mechanical proposals applied (manual items remain)')
else console.log(`garden done${hash ? ` @${hash}` : ''}`)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread bin/fde.js
Comment on lines +1914 to +1920
if (!inRetired) {
const m = t.match(/^-\s*\[\d{4}-\d{2}-\d{2}\]\s*(?:\[@[^\]]+\]\s*)?(.*)$/)
if (m && echoTexts.has(m[1].trim())) {
retiredLines.push(raw)
moved++
continue
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Duplicate-risk cleanup can retire every copy, dropping a live risk from the open list

When two open risks have byte-identical text, the cleanup moves all of them to the retired list (echoTexts.has(m[1].trim()) at bin/fde.js:1916) instead of keeping the first, so a still-open risk can vanish from the active list entirely.
Impact: A live open risk is fully retired and no longer surfaces as open, so it can silently drop off the risk radar before ship/close.

Echoes matched by text-equality, not position

applyRiskDedupe builds echoTexts from group[1..] of each cluster (bin/fde.js:1898-1901), intending to keep the first occurrence and retire later echoes. But it then retires ANY bullet whose stripped text is in echoTexts (bin/fde.js:1916). If the first bullet's text is byte-identical to a later echo (e.g. the test fixture's - [2026-05-01] PLC vendor firmware drift on line 3 and - [2026-05-20] PLC vendor firmware drift on line 3), the first bullet's text is also present in echoTexts (added via the later duplicate), so the first bullet is retired too. The whole cluster gets moved under ## Retired, leaving no open representative. The test at test/fde-cli.test.js:1507-1508 only asserts openBullets.length < 4, so it passes despite this. Matching should be by position/occurrence count (keep the first matching line, retire only subsequent ones) rather than by set membership.

Prompt for agents
In applyRiskDedupe (bin/fde.js), the dedupe logic is intended to keep the first open-risk bullet per fingerprint cluster and retire only the later echoes. It builds echoTexts from group[1..] and then retires any bullet whose stripped text is a member of echoTexts. This breaks when the first bullet and a later echo share byte-identical text: the first bullet's text is in echoTexts (contributed by the later duplicate), so the first bullet is also retired, and the entire cluster is moved under ## Retired leaving no open representative. Fix by deduping based on occurrence/position rather than raw text-set membership: for each cluster, track which fingerprint has already had its first occurrence kept, and only retire subsequent bullets. For example, maintain a per-fingerprint 'seen' set as you iterate the risks bullets, computing riskFingerprint(m[1].trim()) for each open bullet, keeping the first bullet of each cluster fingerprint and retiring the rest, so identical-text duplicates still leave one open risk on record.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@suboss87
suboss87 merged commit 5cbdb3d into Main Jul 21, 2026
2 checks passed
@suboss87
suboss87 deleted the fix/silent-ledger-and-field-gaps branch July 21, 2026 02:33
suboss87 added a commit that referenced this pull request Jul 21, 2026
Make corrupt memory git a loud doctor/garden failure, caveat dirty receipts, cap smart-debrief previews, route Decided: to decisions, and garden-dedupe open risk echoes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant