garrytan/gbrainmarkdown explorer
garrytan/gbrainmaster
skills / migrations

v0.32.2

skills/migrations/v0.32.2.md


version: 0.32.2 feature_pitch: headline: "Facts join the system-of-record — your hot memory now lives in markdown" description: "v0.31 added hot-memory facts but they lived only in the database. v0.32.2 fences them in markdown so they survive gbrain rebuild. Migration v0_32_2 backfills existing facts to entity-page fences. Plus a 3-layer privacy strip and a CI gate that enforces the rule going forward." recipe: null tiers: null

v0.32.2 Migration: facts join the system-of-record

What this is

v0.32.2 closes a gap that's existed since v0.31 shipped hot-memory facts. Facts were DB-only — drop the facts table and the data was gone. v0.32.2 makes them FS-canonical: every fact write lands in a ## Facts fence on the entity page first, then stamps the DB index. The DB is now a true derived cache for facts.

What the agent needs to do

For existing v0.31 brains (most users):

  1. Run gbrain apply-migrations --yes to invoke the v0_32_2 orchestrator.
  2. The orchestrator runs in two phases:
    • phaseAFenceFacts walks every legacy fact row (row_num IS NULL, entity_slug IS NOT NULL) and appends it to the entity's markdown fence atomically. Dry-run by default — runs in dry-run mode first, reports what WOULD happen, then runs again with --write after user confirms (or pass --write directly to skip the confirmation step).
    • phaseBVerify re-parses every touched fence + diffs against the DB. Mismatch → marks the migration partial; user runs gbrain apply-migrations --force-retry 51.
  3. Refuses to run when any source's local_path has uncommitted git changes. Mirrors the v0.14.1 dry-fix safety posture so the user can review the diff before committing.
  4. After completion: git diff shows the new fences; user can commit them and the facts now live in git alongside the rest of the brain.

For brand-new v0.32.2 installs (rare):

The schema migration v51 creates the columns from the start (fresh-install parity via the v40 CREATE TABLE block). The v0_32_2 orchestrator has nothing to backfill (no legacy rows). It runs phaseA + phaseB and no-ops cleanly.

For thin-client installs (no sources.local_path):

Facts continue to write to the DB directly with a once-per-process stderr warning naming the missing sources.local_path. The architecture doc names this as the explicit DB-only exception. To enable fence writes, configure local_path via the source setup.

Things that changed for the agent

  1. gbrain forget <id> now rewrites markdown. Previously gbrain forget called engine.expireFact(id) which UPDATEd the DB only. After gbrain rebuild (v0.32.3) the forget would evaporate. v0.32.2 makes forget rewrite the fence: strikethrough the claim, set valid_until=today, append forgotten: <reason> to the context cell. New optional --reason <text> flag.

  2. MCP get_page strips private fact rows for remote callers. Previously the strip fired only when the caller carried a takes- holders allow-list (a subagent privacy hole). v0.32.2 fires the strip whenever ctx.remote === true. Local CLI sees the full fence; subagent / remote MCP see world-only facts.

  3. The chunker strips private fact text. chunkText calls stripFactsFence({keepVisibility:['world']}) alongside the existing stripTakesFence before chunking. Private fact text never reaches content_chunks.chunk_text, never gets embedded, never returned by search.

  4. CI invariant gate. scripts/check-system-of-record.sh is wired into bun run verify. It bans direct calls to engine.insertFact, addLink, addLinksBatch, addTimelineEntry, upsertTake, insertFacts, expireFact in src/ + scripts/. Legitimate sites carry // gbrain-allow-direct-insert: <reason> comments on the same line. New code that tries to bypass the reconcile layer fails the build.

  5. New cycle phase extract_facts runs between extract and recompute_emotional_weight. Reconciles the DB facts index from the fence on every affected entity page. Empty-fence guard refuses to run while v0.31 legacy rows are pending the backfill (a belt to the orchestrator's suspenders).

Verification

After the agent runs the migration:

# Migration ran cleanly
gbrain apply-migrations --yes

# No fence write failures
test ! -f ~/.gbrain/facts.write_failures.jsonl || echo "WARN: some failures recorded"

# Each entity page with v0.31 facts now has a fence
ls people/ companies/ | head -5
grep -l 'gbrain:facts:begin' people/*.md | head -5

# Doctor passes
gbrain doctor

Things to NOT do

  • Don't manually edit the v51 columns. row_num and source_markdown_slug are managed by the fence-write path. Edit the markdown fence instead; the cycle phase reconciles.
  • Don't call engine.insertFact / expireFact from new code paths. The CI gate blocks it. If you genuinely need a direct write (e.g., for a new reconciler), add the // gbrain-allow-direct-insert: <reason> comment with a justification.
  • Don't delete the facts.write_failures.jsonl log without reading it. It records pages where the fence parse-validate failed during a write; the .tmp file remains as quarantine evidence.

See also

  • docs/architecture/system-of-record.md — the canonical contract doc + the 3-layer privacy boundary
  • CHANGELOG.md v0.32.2 entry — the release manifesto + numbers
  • scripts/check-system-of-record.sh — the CI gate enforcing the rule going forward
Continue exploring589 Markdown documents in the local repository