Repository navigation
v0.60.290.0 Wave B PR 2: segmented reads, privacy, forget/purge, export/backup, derived passes (switch stays off) - #6900
Merged
Conversation
…ery and credential reseal on segmented pages (spec 11) Segment-by-segment fence rewrites (page-body/segment-rewrite.ts) back forget strikes and purge drops on segmented pages: a new generation whose flip replaces only the rewritten occurrences' chunks, retained generations rebuilt under their own number. Fact purge also aborts stagings holding the claim, cancels their body_ref requests and deletes unreferenced texts in the same transaction; receipts probe segment texts. Page purge and the TTL hard delete tombstone streamed hashes and sweep stagings and segment texts. Withdrawal discovery scans facts-flagged segments; credential reseal covers pem regions. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
readBodyRange streams a segmented page's body one segment at a time in stored or projected space for trusted and remote audiences. Remote and projected reads project whole segments (stored protected regions, the withdrawal overlay per segment, the fence overlay with the low-tier block), recompute from the entry state when metadata is stale, and refuse with page_projection_stale rather than serve raw text. Fixes two PR 1 blockers: sealed segmented pages now stamp the markdown chunker version (>= SAFE_FENCE_CHUNKER_VERSION), so remote search and chunk windows serve them; and sealSegmentedPage indexes the timeline lead after the withdrawal overlay. Segment chunks are built from the same projection after the withdrawal overlay. Tests: projection parity fuzz, remote window privacy, sealed segmented search (PGLite and Postgres arms). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…lit; segmented forget strike matches inline first-fence semantics Withdrawal discovery no longer skips a facts slot split by a forced cut: the fence is joined from its slots' bytes (refused above PAGE_MAX_BYTES), so a private row that never reaches chunks is found. The segmented forget strike now considers only the first facts fence (truth for the DB body, truth then timeline for the file mirror), as strikeFenceRow does on the whole string; FenceTransform receives the fence's body region. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
PAGE_BYTES_SQL reads body_bytes for a segmented page (its body columns are empty, so it was never a candidate), and reindexPage rewrites from the materialized snapshot (every candidate is within the ceiling). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
reembedPageWithContextualRetrieval returns skipped/page_body_segmented for a segmented page (it reported page_missing) and stamps nothing; the per-chunk job result carries skip_reason. Per-segment synopses are PR 4 (spec 8.6). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
With pages.segmented_storage on, freezeEntry freezes a Markdown file over the sync read bound by reference (intent.body_ref: sha256, bytes, Git blob id, hashed as a stream) instead of holding it; the owner streams it through the segmented import and publishes database-only (sync-streamed.ts). Renames, git-root subdirectory sources, working-tree files and an owner crash between preparation and publication are covered; a refused file is held with the refusal's code; a dry run lists it as screened, not held. Switch off: no change. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ces a forced cut split; segmen Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> # Conflicts: # docs/architecture/key-files/core-services-2-continued.md # test/postgres-unit-arms.txt
…fill and contextual re-embed on segmented pages Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> # Conflicts: # test/postgres-unit-arms.txt
…e B PR 2, lane G) - Generation readers (embed queue, unsealed chunk reads, segmented embed list/count/install) use chunkGenerationFilter: a row is live only while its occurrence holds a live manifest row, and inline rows only on a page that is not segmented, so rows a deferred retirement or a demotion leaves behind are hidden before they are deleted. Search keeps a subplan-free predicate. - stageSegmentChunks refuses (revision_changed) once the page moved past the staged generation or was demoted; demotion's page row lock orders it after an in-flight batch. - stageSegmentTexts refreshes created_at on a deduped text (ON CONFLICT DO UPDATE), and the text sweep keeps texts a live staging may still reference. - A page-scoped sweep deletes only the texts that page's manifest released. - raw_byte_start/end are relative to the occurrence's segment, so a shifted occurrence keeps valid spans. - v249 drops content_chunks.retired_generation (never written) and builds content_chunks_generation_idx for the staged-chunk GC delete. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…neration dropped Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> # Conflicts: # src/core/page-body/segment-chunks.ts # test/helpers/segmented-chunks-fixture.ts
Export read a segmented page through the throwing body getters and failed (page_body_segmented); its per-page estimate summed NULL body columns, so the 32 MiB limit never saw a segmented page. - export-snapshot: segmentedExportMarkdown streams serializeMarkdown's head, the trusted stored body (readBodyRange) with export's withdrawal overlay and the timeline separator, with a full collection per 1 MiB read. - export-stage: addStream writes a page to its own stage file (no SQLite BLOB), bounded by the payload limit and the staging filesystem's free space. - export: estimate is body_bytes for segmented pages; per-page limit is exportPayloadLimit(pages.max_bytes) (32 MiB while the switch is off); a streamed page extends the snapshot deadline by its bytes. - native: publishExportFileFrom(handle, path, absoluteSource) copies a staged file in 1 MiB reads; eight prebuilds rebuilt with pinned Zig 0.14.1. - read-range.ts is now wired in: orphan-module permit removed. - test/e2e/large-pages-export.test.ts: 2/20 MiB inline vs segmented same sha and export(import(F)) == F on both engines, streamed-ingest trim, abort and deadline, PGLite backup restore, gated 128 MiB memory arm. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…earch dedup Evidence delivery reads windows around the hits of a segmented page through the segment reader (segmented-evidence.ts) instead of falling back to the hit chunk; getChunkWindows reports segmented pages and chunk occurrence ids. Bounded leads overlay a segmented page's stored truth_lead alone, and the exact-slug and alias snippets read it (pageLeadSource). Search dedup keeps two hits per occurrence cluster and six per segmented page; inline pages keep two. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ented page Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> # Conflicts: # docs/architecture/key-files/core-services-2-continued.md # src/core/engine-sql/chunks.ts # test/fixtures/goldens/sql-text/chunks.json
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> # Conflicts: # src/core/facts/purge-verify.ts # src/core/facts/purge.ts
…n gate (Wave B PR 2, lane S) pages.segmented_storage defaults to on. readPageBodyPolicy turns it on only while no writer older than SEGMENTED_WRITER_FLOOR (or unstamped) committed a persistence request in the last seven days; the gate is cached a minute per root engine. New doctor check segmented_storage reports configured, effective and blocking hosts. A streamed page is judged against its page ceiling when content_sanity.bytes_block is unset and its prose pass samples head, middle and tail windows instead of holding the file. Export sizes stored segmented pages by the engine ceiling, so they still export while the switch is off. BEHAVIOR_CHANGES rows (since TBD) and docs. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…l-pass mode) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…' into capy/large-pages-2e
…own digests, streaming gbrain get get_page reads a segmented page in projected bytes without materializing it: none/auto/outline/window (lines, bytes, cursor, around_chunk_id) and view: 'markdown', cut by page-window/segmented.ts from stored sums and only the segments a read touches. body: 'full' is page_body_segmented locally and page_too_large_for_inline over a transport. page_view_digests (v250) caches the read view's line count and the markdown view's size and sha256 per revision and audience. The local gbrain get streams a segmented page segment by segment; the thin client pages, verifies and prints window by window; --json above PAGE_MAX_BYTES is refused with a fix that drops it. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…' into capy/large-pages-2s Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> # Conflicts: # src/core/behavior-change-notice.ts
…F G H I 2d 2b) into lane 2a Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> # Conflicts: # docs/architecture/key-files/core-search-2.md # docs/architecture/key-files/core-services-2-continued.md # src/core/schema-migrations/latest.generated.ts # src/core/schema-migrations/registry.generated.ts # test/postgres-unit-arms.txt
…time The sweep test compared CPU time at 1,000 and 10,000 source texts. It now counts the rows every sweep statement's plan touched (EXPLAIN ANALYZE, rolled back to a savepoint, then the statement runs) and requires the same count at both sizes. The old source-wide text delete reads 1020 rows against 10020. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ine, ceilings and docs for streamed derived passes Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
… outline line lookups Per the coordinator rule, read-cost asserts deterministic bounds instead of a CPU-time ratio: segmentedReadWork (@internal) counts the units and segment bytes a get_page read visits, and a readSegmentTexts spy the text it loads. The counter showed the outline page rescanning a unit from its start for every entry's line number (54.9 MB scanned for 1.6 MB loaded); line lookups now carry the count forward. The manifest-row json_agg (a CPU-only optimization in read-range.ts) is reverted. Doctor content goldens take page_view_digests' columns. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…E_DEFAULT; fallout fixes
- limits.ts: SEGMENTED_STORAGE_DEFAULT ('off' in this commit) decides an unset
pages.segmented_storage; segmentedStorageRequested() is the one place.
- doctor segmented_storage: unset with the default off is an info row with the
enable fix.
- remember on a segmented entity page reads it through materializedPage and
segmentedOverCeiling instead of assertInlineBody; put_page omits
chunk_skip_reason while the projection is pending.
- export: inline pages use EXPORT_PAYLOAD_LIMIT, stored segmented pages the
engine ceiling, without an extra policy read.
- Tests that need segmented storage set the switch explicitly; Wave A
inline-behaviour tests pin it off; size-state receipts run both ways.
- credential-reseal-segments: build the PEM header at runtime (gitleaks).
- Docs and the switch BEHAVIOR row describe the opt-in.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…er v0.60.252.0) into capy/large-pages-2s
…e inside each bounded attempt searchVector and explainVectorSearch ran chunkManifestReads through engine.executeRaw before any attempt's transaction. The probe names content_chunks, so on a fresh engine a session holding ACCESS EXCLUSIVE on it held the search with no bound (it hung). The engine now builds the statement in both chunk-manifest forms on first use (vectorStatementForms in search/vector-statement.ts; the forms differ only in SQL text) and each attempt, and the hasMore witness, picks its form with the probe it runs inside its own transaction, after the settings. The attempt's statement_timeout then bounds the probe: under the lock searchVector returns an incomplete pool (reason deadline) after the walk and pool budgets, and explainVectorSearch fails 57014 at its 8 s attempt budget. test/vector-manifest-probe-bound.test.ts: a recording-handle arm (no statement outside a transaction names content_chunks; every probe follows its attempt's statement_timeout) and a Postgres arm with a real locker session, listed in test/postgres-unit-arms.txt. The vector-chunk-statistics E2E calls the private runVectorAttempt with the new statement-form argument. postgres-engine.ts ceiling 3091 -> 3097. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…s statement_timeout
…read run under the keyword statement bound The explain_target probe (ops/search.ts prepareExplainTarget: the chunk-manifest probe plus probeTarget) and the segmented-region read (search/dedup.ts segmentRegions: the chunk-manifest probe plus the region SELECT) ran through engine.executeRaw with no statement_timeout. Both name content_chunks, so on a fresh engine a lock held on it stalled the search op's setup until the lock went, and segmentRegions for as long as it lasted. New engine member BrainEngine.keywordBoundedRead(read): Postgres runs read in one unscoped withScopedReadTransaction with statement_timeout set to the keyword knob (search.keyword_statement_timeout_ms, the connection's resolved setting, file plane over DB plane); PGLite runs read on the engine itself (no statement timeout, as for its keyword statements). Each site now runs its probe and its statement through it; their existing fallbacks (bare trace, inline rule) answer a cancelled statement. test/search-read-keyword-bound.test.ts (listed in postgres-unit-arms.txt): locker session holding ACCESS EXCLUSIVE on content_chunks, fresh engine, 30 s watchdog, knob 1000 ms. Goldens regenerated: EO8 classification (keywordBoundedRead, lifecycle/raw seam) and the RLS inventory (33 call sites, 33 methods). Ceilings: engine.ts 2823 -> 2832, pglite-engine.ts 3075 -> 3080, postgres-engine.ts 3097 -> 3102. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…statement-timeout retry never re-streams its file The unread (over-ceiling) restore ran its seal probes (occurrencesWithoutChunks, hasStaleChunks: one index probe per live occurrence) inside the publication transaction, under declareDurablePersistence's 5 s statement_timeout. On a loaded host the stale-chunk probe was cancelled (57014); the failure is transient, so recovery requeued the request and the owner prepared it again and streamed the whole file again, once per cancelled attempt (CI stress: 2x and 4x for a 90 MiB restore, one cancel per extra write). The probes now run in preparation; the transaction only seals. Exact, because publication requires the observed revision and a soft-deleted page gains no chunk rows. Forced probe: restore-publication-statement-bound.test.ts fails the first seal probe a publication transaction runs with 57014; before: bytesWritten 2x on both engines, after: once, sealed, no probe in the transaction. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ed seal check never re-streams the file
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> # Conflicts: # docs/architecture/key-files/commands-3.md # scripts/module-size-limits.tsv # src/core/cli-flag-registry.generated.ts # test/fixtures/goldens/cli/tools-json.json # test/fixtures/goldens/exports/types.json
… keyword-bound read Drops the explain_target half of 4d3a3b0: prepareExplainTarget (ops/search.ts) is back to its 995af5b form, and test/search-read-keyword-bound.test.ts loses the search-op setup-stage probe and the op check after the lock. keywordBoundedRead stays, now documented for the segmented-region read only, with segmentRegions as its one caller. Upkeep re-derived for the dedup half: keywordBoundedRead is still one engine member with one withScopedReadTransaction call site, so the EO8 classification and RLS inventory goldens regenerate unchanged (33 call sites, 33 methods), engine-surface-coverage and DOMAIN_OF keep the entry, and the module ceilings stay at the real line counts (engine.ts 2832, pglite-engine.ts 3080, postgres-engine.ts 3102); only rationale text changes. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…he master 4e29d09 merge Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
… the keyword knob (keywordBoundedRead)
…bound) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…e-searchable tests A put_page in an earlier test left this process's persistence consumer running on the shared engine. Its resident projection route claimed the row the drain test lapses to model a dead process, so the drain rightly skipped a live claim (rebuilt 0), and that build then reached the next test's unfiltered projection hook (2 projections). beforeEach now disposes the consumer, which waits for its resident build; put_page restarts it. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Master took v251 (take_horizons, #6894 / W7) first. PR 2's five migrations move up by one in the same order: segmented_chunk_gc_index 252, page_view_digests 253, segment_reader_marks 254, page_body_staging_texts 255, chunk_retirement_index 256. Registry, latest, schema blob and the migrations golden are regenerated; test/large-pages/v251-master-reader-compat follows its migration to v252. On its own this commit leaves 251 as a gap (migrations-golden's gap test); the merge with master's v251 closes it. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Conflicts: union of memory-purge key-files doc; generated registries, goldens and Postgres doctor goldens regenerated; graduation-inventory counts from merged entries; postgres-arm-weights.json re-mined from the 16 d406e03 8-shard arm logs. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
The stress gate packs shards by per-file estimates and splits a file's iterations only when ten would exceed the 25-minute budget. 19 of PR 2's stressed files had no weight in their lane map and were packed at the p75 fallback; e2e/large-pages-export (about 330-390 s an iteration, p75 8.5 s) shared shard 12 with other files and ran it out of its 35-minute timeout. Measured sources, added only for files with no entry: - e2e (5): Selected E2E run 39030300996 on 717a152 - unit (12) and serial (1): test run 39026719688 on d406e03 - slow (2, unit map): ci:ubicloud slow lane on 717a152 postgres-arm-weights.json is re-mined from the 32 measured 8-shard arm logs of d406e03 and 717a152, which adds v252-master-reader-compat and master's two new arm files. Planner and budget unchanged; large-pages-export now splits 3/3/3/1 across four shards. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
v248 is on master (Wave B PR 1). Lane G dropped retired_generation from its ANALYZE when v249 still dropped the column; the column now stays, so the published payload is restored unchanged (release:restamp refuses an edit to a published migration). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…gmented-reads Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> # Conflicts: # scripts/test-weights.json # src/core/cli-flag-registry.generated.ts # test/fixtures/goldens/exports/types.json
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Master's re-mined test-weights.json (run 39028606998) predates PR 2's unit-lane files, so 79 of them were partitioned at the p75 fallback and the 10-shard matrix projected 772-975 s per shard. Adds their measured durations from the PR's own green unit run 39026719688 (d406e03), only for files with no entry; the 10 shards now project 808-903 s. Drops the stale v251-master-reader-compat arm key (renamed to v252). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ubbing it as VERSION SEGMENTED_WRITER_FLOOR equals VERSION in the release that stamps it, so the version scrub turned it into <version> there and would turn it back into a literal one release later, failing every doctor golden on the next PR. The doctor JSON normalizer now maps a floor field equal to the constant to <segmented-writer-floor>; other floors (0.60.5.0) stay literal. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…s its recovery record The purge's reply is sent when the write commits; the publication then removes its staged prior file and clears the recovery record, releasing the reservation (coordinator.ts, clearResolvedRecovery). recovery-budget-ceiling read the counters right after the reply and saw the purge's reservation (file + record, on the brain and worktree counters: 419439864 on Postgres) when the reply won that race (stress gate, run 39040910917, iteration 3). Both large-file round-trip tests now wait for the source's requests to clear their recovery records, then assert the counters are exactly zero. Forced probe: a 3 s stall at publication:after_commit for delete_page fails the old test on PGLite (268444916) and Postgres (419439864) and passes with the fix on both. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Closed
pasogott
pushed a commit
to pasogott/gbrain
that referenced
this pull request
Oct 17, 2026
…an#6900 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
pasogott
pushed a commit
to pasogott/gbrain
that referenced
this pull request
Oct 17, 2026
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.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.
Large-pages Wave B, spec §13 PR 2. Builds on PR 1 (#6787, v0.60.252.0). Every path that reads, rewrites, exports or derives from a page body now works on a page stored in segments.
pages.segmented_storagetakes effect only when no recent writer predates segment readers. The default staysoffin this PR. The flip is a separate, held commit (037247627oncapy/large-pages-2s), to land after the switch-on blockers under Known gaps are fixed. GBRA-60's trust-accept fix is on master (#6807), and the both-engine test for an accepted held take or timeline removal on a segmented page is in this PR.Stamped v0.60.290.0 on master v0.60.289.0 (15b1595): VERSION, CHANGELOG, every version stamp,
SEGMENTED_WRITER_FLOOR= 0.60.290.0, andsince: '0.60.290.0'on the 32 BEHAVIOR_CHANGES rows this PR adds. Migrations stay v252–v256 (master's latest is v251). Master's published v248 payload is restored unchanged; an earlier lane had edited its ANALYZE column list.What's in it
get_pagenone/auto/outline/window/markdown,fetch,entity, evidence and the CLI render all read segments throughreadBodyRangewith the projection overlay (withdrawals, fences, credentials).fullrefuses with a fix pointing atbody:'auto'. Every read that follows an authorization is pinned to the authorized generation; a visibility flip or trust demotion mid-read refuses. A manifest that doesn't tile the body refusespage_body_rows_unreadablefor every byte-producing reader (export, backup, restore, digests, materialize, rewrites, projection), so a short read can't pass as complete. Remote callers see projected sizes only (get_page, get_versions).docs/ENGINES.mdtested verbatim by a two-source NOBYPASSRLS e2e. With the switch off and no segmented pages, search, get_page, put_page, remember, sync, embedding effects and the unchanged-file check need no segment-table grant. Five maintenance paths do (purge-deleted, forget/purge_fact discovery, the sweep's facts pass, doctor's credential check, the segment storage sweep); a BEHAVIOR row and the docs name them.purge_factrewrites segments and manifests across every generation, in keyset batches. Forget is scoped to the segment and occurrence. Withdrawal discovery joins split facts fences. Purge, the 72 h expiry and GC never delete a text an in-flight create holds (page_body_staging_texts, v255);purge_factaborts a staging that holds the claim.pages.max_byteson file-backed sources. The drift check streams per-region digests. Restore streams the file from segments. The pre-push secret scan reads large files in bounded windows, with claims owned by the window holding the match anchor. The recovery record stages the prior file by path and sha256 instead of embedding it, and the before copy is written to.copyingand renamed after fsync.exportStreamWork. PGLite backup restores segmented pages.searchable: "pending"with a retry interval and fix; one build runs under a 60 s queue claim plus master's in-process single-flight.projections drainrebuilds segmented pages. Retirement is lock-and-recheck per batch and runs on a skip scan over the v256 index.segmented_storagereports it.add_tag/remove_tagflip metadata on a segmented page at any size: no segment, manifest or chunk write, and the hash resumes from the last manifest row's saved state. Timeline, takes, facts, sources reconcile and core add/diff read the page whole up to 24 MiB and write it as a normal put, matching the inline result. Above 24 MiB they refuse, naming the limit.worktree_recovery_bytesdefaults to 2 × (pages.max_bytes+ 1 MiB), never below 256 MiB (514 MiB on Postgres). A restore reserves the file twice, so a page at the ceiling now restores. An explicit value is kept.oversized_pages,scraper_junk_pages,markdown_body_completeness,link_resolution_opportunity,fence_integrity,core_memory,extract_health) andgbrain repair frontmatterreportskipped_segmented: N/ "skipped (segmented): N". The field is absent when N is 0, so the goldens are unchanged.purge_factsaves a preimage version for a segmented page (pointing at the purged generation, as the belief ledger's page-at-time expects); the restore-time purge replay and page-scope replay delete segment texts the way purge does. The core list and frontmatter re-import read segmented pages up to 24 MiB; above it the core list skips the page (doctor counts it) and the re-import isneeds_review.Migrations
Master's v251 (
take_horizons) comes first; this PR's five take the next free numbers, v252–v256, in their original order.retired_generationis kept; master's readers select it)page_view_digestspersistence_requests,persistence_consumers,ingest_logpage_body_staging_texts; re-addscontent_chunks.retired_generationif a test brain dropped itcontent_chunks_occurrence_generation_idx (page_id, occurrence_id, body_generation)Measured
100 MiB bench, final tree (
scripts/bench-large-pages.ts --size 100MB, one seeded conversation page, MemoryMax=3G):Retirement plan (v256): half-rewrite selection at 26 MB went from 2.18M to 1.8k buffers on Postgres. The crash-recovery probe at 100 MB went from 421k to 3.8k.
N14 findings (gbrain-evals#165): the 2 MB chat append went from 2 chunks re-embedded to 1 (ratio 4.22 → 1.30); the other fixtures are unchanged. Time to searchable at 20 MB: the duplicate build is gone, and a page up to 4 MiB is searchable at commit.
All cost and linearity assertions use work counters, EXPLAIN rows/buffers, byte spies or retained heap after GC. None uses CPU time or wall clock.
Validation
recovery-budget-ceiling.slowread the recovery counters right after the purge's reply, but the reply is sent at commit and the recovery record is cleared after that (clearResolvedRecovery). The test and its 27/90 MiB sibling now wait for the source's recovery records to clear, then assert the counters are exactly 0. Forced probe: a 3 s stall atpublication:after_commitfails the old test on PGLite and Postgres and passes the fixed one on both.test-weights.jsonand are added from the PR's green unit run 39026719688; the 10-shard matrix projects 808–903 s per shard. Stress estimates: 20 unweighted stressed files got measured weights, soe2e/large-pages-export(about 330–390 s per iteration) splits across four stress shards.<segmented-writer-floor>, so they don't flip between<version>and a literal when VERSION moves past the floor.failed), never get the media flag, are never sealed bysealImageProjections, and are drained by the resident andprojections drainoutside the 500-row window (forced probes on both engines intest/large-pages/gbra117-segmented-classes-window.test.ts).scanUncollected≤ 5 MiB probe); the 32 MiB delta fell from ~310 to ~230 MiB with the 450 bound unchanged.gbrain trust confirm's path and the text stays out of segments, chunks and search.<sync-day>like the fence trend day (forced probe intest/doctor-hot-checks-sync-day.test.ts)./review(Opus) and an outside voice (Grok 4.7; GPT-6 Astra refused under its cyber policy and Gemini rejected the request). Every finding was fixed with a forced probe that fails before and passes after on both engines, or shown not to reproduce.get_versionson demoted pages, managed stale-link extraction holding every timeline entry).page_body_segmentedrefusal (about 900 probes on both engines) drove the 2r refusal fixes.Contract golden changes
test/fixtures/goldens/exports/{runtime,types}.json) gainkeywordBoundedRead, the one engine member added sosearch/dedup.tssegmentRegionsruns its manifest probe and region read under the keyword knob'sstatement_timeout.Known gaps (not in this PR)
applySegmentedBody(segmented-apply.ts:184) runshasStaleChunksinside the publication transaction, so a large segmented write on a loaded host can be cancelled at the 5 s statement bound and re-prepared; for the flip PR.A transient publication failure (57014 and the other transient codes) re-runs the whole prepare with no limit and outside
max_preparation_attempts; for the flip PR.Switch-on blocker: the canonical file mirror (
mirrorPage) skips segmented pages, so afterpurge_facta segmented page's file on disk keeps the purged claim. It must be fixed before the flip.Switch-on blocker: the projection claim's
renewinbuildClaimedProjectionrefreshes the queue row without checking that this builder still owns the claim, so a build that outlived its 60 s claim keeps renewing and staging chunks next to the builder that took the claim over. It must be fixed before the flip.A restored archive holding a running request whose staging holds a purged claim makes the replay refuse with
purge_blocked_pending_recovery, which fails the restore.Dev brains that ran this branch before either renumbering treat a master migration at a reused number as applied: before the first (old v249) master's v249 belief ledger, before the second (old v251, this PR's GC index) master's v251
take_horizons. Recreate them. Released brains are unaffected.revert_versionon a segmented page refuses until PR 3 (Garry accepted; BEHAVIOR row added).edit_pageandcaptureonto an existing slug over 24 MiB refuse (fence scan at admission, no-op check).Pages over 24 MiB stay keyword-only (oversized embed skip); imports of person/company/fenced pages over 24 MiB are held for the write gate.
A brain that ran with the switch on and later turned it off falls back to the 256 MiB recovery budget, so its Postgres pages over about 128 MiB can be deleted but not restored until the switch is back on.
gbrain repair stuck-recoverylists a large-file recovery as refused; the resident writer finishes it.Older binaries fail closed (
unexpected_staging_bytes) on a recovery record that carriesstaging.before.The gate can't see an older binary that writes without managed persistence and leaves no request or ingest row;
pages_body_storage_chkstill fails old whole-body writes closed.Dropping
retired_generationfor real waits for a later migration, once the floor excludes master's binary.The sweep defers a page too large for any budget to
gbrain extract --stale; the deferral memory is per process.