You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I worked through a backlog of queued memory proposals in one sitting. Thirteen rows, roughly a month of accumulation. Seven of them were correct captures pointed at the wrong destination file: the reviewer had targeted the always-loaded operational rules file for content that belonged in a skill, in a project file, and in a domain-specific document.
The right outcome for those seven was to write the content where it belonged and close the row. The queue offers no way to say that. The status union in LIFEOS/PULSE/lib/memory-proposals.ts is:
accepted is untrue, since the recorded target_file never received the edit. So the only honest-ish option is rejected, and the log now says seven durable user statements were discarded when their content is live in the files where it belongs. The record reads exactly backwards from what happened.
Why this is worth fixing rather than tolerating
There is no runtime consequence today, and that is precisely the trap. The reviewer does not read the queue at all: it has no awareness of what was previously proposed, accepted, or rejected, so a wrong label cannot currently bias anything.
But "feed prior decisions back to the reviewer so it stops re-proposing what the user already ruled on" is an obvious next feature, and it is the natural fix for a reviewer with no proposal memory. The moment someone builds it, these mislabeled rows become its input, and it learns to suppress exactly the proposals the user adopted. The corruption is being written today and consumed later.
This is a silent-wrongness failure rather than a crash. Nothing errors, nothing alerts, and the log stays plausible enough that nobody re-reads it.
The second half: terminal statuses are stringly-typed and hand-rolled, and shipped consumers already disagree
There is no single exported authority for "which statuses are terminal", so every consumer writes its own list. On current main this is not a hypothetical risk; the shipped consumers have already drifted apart:
LIFEOS/PULSE/modules/memory.ts computes pending rows by excluding a literal array, ["auto-applied", "accepted", "rejected", "edited"]. The comment above it records that this exact line was already fixed once, for fix(pulse): pending-proposal badge never clears — only auto-applied counts as resolved #1610, after an earlier version counted resolved rows as pending forever. The fix corrected the values but not the shape.
LIFEOS/PULSE/modules/menubar.ts counts pending as status === "pending" only. It silently drops sent rows, which the dashboard deliberately counts as awaiting a decision. Two surfaces shipped in the same release disagree about what "pending" means.
LIFEOS/TOOLS/MemoryReviewer.ts writes auto-apply-failed, a status that is not in the union at all. The dashboard happens to count it as pending (correctly, per its own comment), but only because unknown strings fall through the exclusion list. Nothing checked that this writer and the type agree.
From a local CLI I built on top of the same library: it wrote approved on its success path, another value outside the union, and gated on approved when checking whether a row was already resolved. Because the dashboard's terminal list does not contain approved, any row resolved that way would be counted as pending forever. That is the same class of bug as fix(pulse): pending-proposal badge never clears — only auto-applied counts as resolved #1610, arriving through a different literal. I am not reporting my CLI as a bug, since it is not shipped code. I am reporting that the library makes the bug easy to write and impossible to catch: a bare string union, no exported terminal set, no helper, no type-level pressure on consumers.
Even the vocabulary confusion shows up in the project's own history: #1610 itself described the human-decision statuses as applied and dismissed, names that have never existed in the union. When the bug report about statuses cannot name the statuses, the strings have no owner.
Suggested fix
Add a terminal status meaning "the content was applied somewhere other than the proposed target", carrying the real destination path. Something like relocated plus a to field.
Give whatever review surface acts on proposals a verb for it, so relocating is one action instead of a manual edit plus a false rejection.
Export the terminal-status set from the module that owns the union, and have every consumer import it:
Then the dashboard, the menu bar, any CLI, and any future reviewer feedback loop agree by construction, and adding a status like relocated is a one-line change rather than a hunt for every hand-rolled array.
Related
#1610 is the same failure mode; this is a request to fix the shape that produced it rather than the values it produced. The missing decision channel (nothing on main can currently write sent, accepted, rejected, or edited at all) is filed separately as #1805, since it is a functional gap rather than a type-design one.
What happened
I worked through a backlog of queued memory proposals in one sitting. Thirteen rows, roughly a month of accumulation. Seven of them were correct captures pointed at the wrong destination file: the reviewer had targeted the always-loaded operational rules file for content that belonged in a skill, in a project file, and in a domain-specific document.
The right outcome for those seven was to write the content where it belonged and close the row. The queue offers no way to say that. The status union in
LIFEOS/PULSE/lib/memory-proposals.tsis:acceptedis untrue, since the recordedtarget_filenever received the edit. So the only honest-ish option isrejected, and the log now says seven durable user statements were discarded when their content is live in the files where it belongs. The record reads exactly backwards from what happened.Why this is worth fixing rather than tolerating
There is no runtime consequence today, and that is precisely the trap. The reviewer does not read the queue at all: it has no awareness of what was previously proposed, accepted, or rejected, so a wrong label cannot currently bias anything.
But "feed prior decisions back to the reviewer so it stops re-proposing what the user already ruled on" is an obvious next feature, and it is the natural fix for a reviewer with no proposal memory. The moment someone builds it, these mislabeled rows become its input, and it learns to suppress exactly the proposals the user adopted. The corruption is being written today and consumed later.
This is a silent-wrongness failure rather than a crash. Nothing errors, nothing alerts, and the log stays plausible enough that nobody re-reads it.
The second half: terminal statuses are stringly-typed and hand-rolled, and shipped consumers already disagree
There is no single exported authority for "which statuses are terminal", so every consumer writes its own list. On current main this is not a hypothetical risk; the shipped consumers have already drifted apart:
LIFEOS/PULSE/modules/memory.tscomputes pending rows by excluding a literal array,["auto-applied", "accepted", "rejected", "edited"]. The comment above it records that this exact line was already fixed once, for fix(pulse): pending-proposal badge never clears — onlyauto-appliedcounts as resolved #1610, after an earlier version counted resolved rows as pending forever. The fix corrected the values but not the shape.LIFEOS/PULSE/modules/menubar.tscounts pending asstatus === "pending"only. It silently dropssentrows, which the dashboard deliberately counts as awaiting a decision. Two surfaces shipped in the same release disagree about what "pending" means.LIFEOS/TOOLS/MemoryReviewer.tswritesauto-apply-failed, a status that is not in the union at all. The dashboard happens to count it as pending (correctly, per its own comment), but only because unknown strings fall through the exclusion list. Nothing checked that this writer and the type agree.From a local CLI I built on top of the same library: it wrote
approvedon its success path, another value outside the union, and gated onapprovedwhen checking whether a row was already resolved. Because the dashboard's terminal list does not containapproved, any row resolved that way would be counted as pending forever. That is the same class of bug as fix(pulse): pending-proposal badge never clears — onlyauto-appliedcounts as resolved #1610, arriving through a different literal. I am not reporting my CLI as a bug, since it is not shipped code. I am reporting that the library makes the bug easy to write and impossible to catch: a bare string union, no exported terminal set, no helper, no type-level pressure on consumers.Even the vocabulary confusion shows up in the project's own history: #1610 itself described the human-decision statuses as
appliedanddismissed, names that have never existed in the union. When the bug report about statuses cannot name the statuses, the strings have no owner.Suggested fix
relocatedplus atofield.Then the dashboard, the menu bar, any CLI, and any future reviewer feedback loop agree by construction, and adding a status like
relocatedis a one-line change rather than a hunt for every hand-rolled array.Related
#1610 is the same failure mode; this is a request to fix the shape that produced it rather than the values it produced. The missing decision channel (nothing on main can currently write
sent,accepted,rejected, oreditedat all) is filed separately as #1805, since it is a functional gap rather than a type-design one.