Phase 2 · world-fact coexistence / supersede-on-accept (#84 carry-forward 1) - #97
Merged
Conversation
…ward 1) Lift the #86 conservative-skip so the T3 harvest keeps an already-accepted working-note subject current. A proposed UPDATE now coexists with the accepted note (which keeps framing); accept supersedes it in place, reject discards. - Schema: relax the table-level UNIQUE(agent_id, subject) to two partial unique indexes (accepted-only, proposed-only), created in migrate() (their WHERE clause references review_state, a column a v0.3.0 DB lacks until the #86 ALTER). - Migration: the first non-additive one — a one-time table rebuild when the v0.3.0 table-level UNIQUE is still present (PRAGMA index_list origin='u'); idempotent; verified on bun:sqlite, better-sqlite3, and node:sqlite. - Repo: getAccepted/getProposed (replace the now-ambiguous get), acceptProposed (supersede-in-place or flip), deleteProposed (reject = discard); count = COUNT(DISTINCT subject); clear deletes all rows for a subject. Removed WorldFactConflictError + the world-fact settleProposed. - Store: proposeWorldFact -> WorldFact|undefined (no-op suppression; coexists, never clobbers an accepted note); accept/reject use the new repo methods. - Harvest: proposes a coexisting update instead of skipping an accepted subject; a no-op return is counted as skipped. - CLI: notes accept/reject resolve the proposed row; notes inspect groups by subject with a pending-update line for a coexisting proposal. Suite 1045 -> 1055, tsc -b clean, verify:node + verify:deno 15/15. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ected rows - [P2] proposeWorldFact: when an observation re-confirms the accepted value and a DIFFERENT pending proposal exists, that proposal is now stale (could later be accepted to a value the world no longer shows). Discard it (audited as a world_fact.cleared) instead of leaving it, then propose nothing. - [P2] migrate(): the coexistence table rebuild now filters out pre-existing `rejected` rows (WHERE review_state != 'rejected'). #86 kept rejected rows, but coexistence discards them and the new code assumes none exist (they would be counted by count() yet invisible to formatting/clear — an unclearable cap leak). A pure pre-#86 DB has only `accepted` rows, so the filter is a no-op there. +3 tests (store stale-clear with audited world_fact.cleared, harvest e2e stale-clear, migration drops a legacy rejected row). Verified the rebuild filter on bun:sqlite, better-sqlite3, and node:sqlite. Suite 1055 -> 1058, tsc -b clean, verify:node + verify:deno 15/15. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
#84 carry-forward 1 of 3 — world-fact coexistence / supersede-on-accept. Lifts the #86 conservative-skip so the T3 harvest can keep an already-accepted working note current, while preserving the no-unreviewed-clobber invariant the skip was protecting.
The naive fix (let a proposed write supersede the accepted row) is the governance regression #86 §11.1 refused — it drops a ratified note from framing on an unreviewed write. To update an accepted note safely, the accepted value must keep framing while the proposed update waits in the review queue → they must coexist per subject.
Model
clear/forget_noteremoves all rows for a subject.Forks settled with the operator
UNIQUE(agent_id, subject).Migration (the notable bit)
The first non-additive migration: SQLite cannot
DROP CONSTRAINT, somigrate()does a one-time table rebuild when it detects the v0.3.0 table-level UNIQUE (PRAGMA index_listorigin='u'). The partial indexes are created inmigrate(), not SCHEMA, because theirWHERE review_stateclause references a column a v0.3.0 DB lacks until the #86 ALTER (SCHEMA runs first). Idempotent; verified on bun:sqlite, better-sqlite3, and node:sqlite — constraint gone, coexistence works, re-open is a no-op.Verification
Suite 1045 → 1055,
tsc -bclean,verify:node+verify:deno15/15.Remaining #84 carry-forwards (separate PRs)
Type-B objective status transitions, per-agent world-fact cap.
🤖 Generated with Claude Code