Skip to content

Phase 2 · world-fact coexistence / supersede-on-accept (#84 carry-forward 1) - #97

Merged
nandanito merged 2 commits into
mainfrom
feat/world-fact-coexistence
Jun 29, 2026
Merged

Phase 2 · world-fact coexistence / supersede-on-accept (#84 carry-forward 1)#97
nandanito merged 2 commits into
mainfrom
feat/world-fact-coexistence

Conversation

@nandanito

Copy link
Copy Markdown
Contributor

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

  • A proposed UPDATE coexists with the accepted note for a subject — the accepted value keeps framing while the update waits for review.
  • Accept supersedes the accepted note in place (same id + created_at, consumes the proposal); a brand-new proposal flips to accepted.
  • Reject discards the proposal (no rejected-history rows — world-facts are volatile current-state, unlike memory/objectives); the accepted note is untouched.
  • No-op suppression (an unchanged re-observation queues nothing); cap counts distinct subjects (an update to a tracked subject never trips it); clear/forget_note removes all rows for a subject.

Forks settled with the operator

  • Storage → partial unique indexes (accepted-only + proposed-only) replacing the table-level UNIQUE(agent_id, subject).
  • Reject → discard (world-facts are volatile; no rejected history).

Migration (the notable bit)

The first non-additive migration: SQLite cannot DROP CONSTRAINT, so migrate() does a one-time table rebuild when it detects the v0.3.0 table-level UNIQUE (PRAGMA index_list origin='u'). The partial indexes are created in migrate(), not SCHEMA, because their WHERE review_state clause 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 -b clean, verify:node + verify:deno 15/15.

Remaining #84 carry-forwards (separate PRs)

Type-B objective status transitions, per-agent world-fact cap.

🤖 Generated with Claude Code

nandanito and others added 2 commits June 29, 2026 20:08
…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>
@nandanito
nandanito merged commit dcc45e0 into main Jun 29, 2026
1 check passed
@nandanito
nandanito deleted the feat/world-fact-coexistence branch June 29, 2026 18:28
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