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):
- Run
gbrain apply-migrations --yesto invoke the v0_32_2 orchestrator. - 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
--writeafter user confirms (or pass--writedirectly 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.
- 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
- Refuses to run when any source's local_path has uncommitted git
changes. Mirrors the v0.14.1
dry-fixsafety posture so the user can review the diff before committing. - After completion:
git diffshows 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
-
gbrain forget <id>now rewrites markdown. Previouslygbrain forgetcalledengine.expireFact(id)which UPDATEd the DB only. Aftergbrain rebuild(v0.32.3) the forget would evaporate. v0.32.2 makes forget rewrite the fence: strikethrough the claim, setvalid_until=today, appendforgotten: <reason>to the context cell. New optional--reason <text>flag. -
MCP
get_pagestrips 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 wheneverctx.remote === true. Local CLI sees the full fence; subagent / remote MCP see world-only facts. -
The chunker strips private fact text.
chunkTextcallsstripFactsFence({keepVisibility:['world']})alongside the existingstripTakesFencebefore chunking. Private fact text never reachescontent_chunks.chunk_text, never gets embedded, never returned by search. -
CI invariant gate.
scripts/check-system-of-record.shis wired intobun run verify. It bans direct calls toengine.insertFact,addLink,addLinksBatch,addTimelineEntry,upsertTake,insertFacts,expireFactinsrc/+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. -
New cycle phase
extract_factsruns betweenextractandrecompute_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_numandsource_markdown_slugare managed by the fence-write path. Edit the markdown fence instead; the cycle phase reconciles. - Don't call
engine.insertFact/expireFactfrom 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.jsonllog 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 boundaryCHANGELOG.mdv0.32.2 entry — the release manifesto + numbersscripts/check-system-of-record.sh— the CI gate enforcing the rule going forward