Skip to content

v0.60.290.0 Wave B PR 2: segmented reads, privacy, forget/purge, export/backup, derived passes (switch stays off) - #6900

Merged
capy-ai[bot] merged 201 commits into
masterfrom
capy/large-pages-segmented-reads
Oct 17, 2026
Merged

capy-ai[bot] merged 201 commits into
masterfrom
capy/large-pages-segmented-reads

Conversation

@garrytan

@garrytan garrytan commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

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_storage takes effect only when no recent writer predates segment readers. The default stays off in this PR. The flip is a separate, held commit (037247627 on capy/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, and since: '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

  • Reads: get_page none/auto/outline/window/markdown, fetch, entity, evidence and the CLI render all read segments through readBodyRange with the projection overlay (withdrawals, fences, credentials). full refuses with a fix pointing at body:'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 refuses page_body_rows_unreadable for 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).
  • Privacy: RLS on the six segment tables, re-asserted on every Postgres schema replay, plus a source-scoped policy recipe in docs/ENGINES.md tested 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.
  • Forget/purge: purge works at any size. purge_fact rewrites 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_fact aborts a staging that holds the claim.
  • Delete/restore: lossless up to pages.max_bytes on 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 .copying and renamed after fsync.
  • Export/backup: export streams segmented pages byte-identical to inline (BOM-safe decode), counted by exportStreamWork. PGLite backup restores segmented pages.
  • Derived passes (full-pass mode): links, timeline, takes, facts reconcile, conversation facts (lossless tail window above 24 MiB), tags, evidence, dedup, think/compile leads, integrity, reconcile-links, reindex --aliases, the sweep (budgeted; a page that can't fit is deferred, excluded in SQL) and stale extraction (list == count). Block reads walk the manifest once; forced cuts at line boundaries start new blocks.
  • Projection: a create up to 4 MiB seals inside its transaction and is searchable on return. A larger create reports 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 drain rebuilds segmented pages. Retirement is lock-and-recheck per batch and runs on a skip scan over the v256 index.
  • Chunking: on segments, a heading leads the next chunk, so an append after a full last chunk re-embeds one chunk.
  • Writer gate: v254 stamps segment-reader marks on requests, publications, consumer heartbeats and ingest_log rows. Any unmarked, below-floor or unparsable writer in the last 7 days keeps the switch off; an unreadable gate counts as blocked. Doctor segmented_storage reports it.
  • Semantic writes (decision feat: GBrain v0.1.0 — Postgres-native personal knowledge brain #1 d): add_tag/remove_tag flip 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.
  • Recovery budget (decision docs: expand brain schema with database architecture and OSS smoothing #4): with the switch on, worktree_recovery_bytes defaults 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.
  • Doctor (decision GBrain v0.4.0 — production agent documentation + reference architecture #10): seven checks that read whole bodies (oversized_pages, scraper_junk_pages, markdown_body_completeness, link_resolution_opportunity, fence_integrity, core_memory, extract_health) and gbrain repair frontmatter report skipped_segmented: N / "skipped (segmented): N". The field is absent when N is 0, so the goldens are unchanged.
  • Restore (decision Feature request: Org-mode (.org) ingestion and sync support #11): a restored segmented page is re-projected by the existing queue trigger and is searchable at commit or after one drain; a regression test pins this.
  • Master interactions: purge_fact saves 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 is needs_review.
  • Ceilings: 256 MiB (Postgres) and 128 MiB (PGLite) when the switch is on; 24 MiB otherwise.

Migrations

Master's v251 (take_horizons) comes first; this PR's five take the next free numbers, v252–v256, in their original order.

# What Lock
v252 GC index on segment texts by generation (retired_generation is kept; master's readers select it) CONCURRENTLY on Postgres
v253 page_view_digests new table
v254 segment-reader marks on persistence_requests, persistence_consumers, ingest_log metadata only
v255 page_body_staging_texts; re-adds content_chunks.retired_generation if a test brain dropped it new table; ADD COLUMN IF NOT EXISTS
v256 content_chunks_occurrence_generation_idx (page_id, occurrence_id, body_generation) CONCURRENTLY on Postgres (0.34 s at a 100 MB page, 0.8 s at 1M chunks); PGLite blocks writes while it builds

Measured

100 MiB bench, final tree (scripts/bench-large-pages.ts --size 100MB, one seeded conversation page, MemoryMax=3G):

PGLite Postgres
Ingest 50.5 s 32.3 s
Peak RSS (over a 1 MiB import) 781 MB (+175 MB) 379 MB (+145 MB)
Rows 374 segments, 29,044 chunks same
10 KB append 4.4 s, 1 segment, 10 chunks 3.6 s, 1 segment, 10 chunks
Small puts during ingest p50 13 ms, p95 623 ms p50 12 ms, p95 33 ms
Flip retiring 40k rows 1.2 s (1.9 s embedded) 0.6 s (1.2 s embedded)

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

  • Full Ubicloud gate green on head 520df8a (v0.60.290.0, contains master 15b1595 with the 10-shard unit matrix): unit 3642, serial 429, slow 54, e2e 618, pg-arms 313, gitleaks, verify, 0 failed. pg-arms shards (8, 1,200 s budget each): 761/688/822/780/720/910/818/876 s.
  • Stress flake fixed: recovery-budget-ceiling.slow read 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 at publication:after_commit fails the old test on PGLite and Postgres and passes the fixed one on both.
  • Postgres arm weights re-mined from the 32 measured 8-shard arm logs of d406e03 and 717a152; every listed arm file is weighted. Projected file time per shard: Bun 1.4.0 at most 893 s (74% of the budget), Bun 1.4.3 at most 864 s.
  • Unit weights: 79 of this PR's unit-lane files had no entry in master's re-mined test-weights.json and 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, so e2e/large-pages-export (about 330–390 s per iteration) splits across four stress shards.
  • Doctor goldens label the segmented writer floor <segmented-writer-floor>, so they don't flip between <version> and a literal when VERSION moves past the floor.
  • GBRA-117's conditions after v0.60.278.0 #6803 wave: drainable projection queue, visible held embedding budgets, bounded doctor/status, extraction nudge (#6847) #6869: segmented rows count as pending in the text class (never failed), never get the media flag, are never sealed by sealImageProjections, and are drained by the resident and projections drain outside the 500-row window (forced probes on both engines in test/large-pages/gbra117-segmented-classes-window.test.ts).
  • Memory-test flake: pass 1 of a streamed import now collects every 4 MiB (scanUncollected ≤ 5 MiB probe); the 32 MiB delta fell from ~310 to ~230 MiB with the 450 bound unchanged.
  • The v0.60.253.0 W1 trust prerequisites: contest guard for lower-tier take/timeline removals; derived-tier down-propagation #6792/v0.60.254.0 fix(trust): accept keeps deactivated/re-kinded takes; accept works on segmented pages #6807 trust accept is covered by a both-engine test: a held take removal and a held timeline removal on a segmented page are accepted through gbrain trust confirm's path and the text stays out of segments, chunks and search.
  • Master's Postgres doctor hot-checks golden pinned the soak-counter day to its capture date; it is now stored as <sync-day> like the fence trend day (forced probe in test/doctor-hot-checks-sync-day.test.ts).
  • Two independent review rounds on every fix wave: gstack /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.
  • The final gstack review's one high finding (a second write sealed a page over the first write's unretired rows, keeping removed text searchable) is fixed with a forced probe on both engines, along with its two mediums (remote get_versions on demoted pages, managed stale-link extraction holding every timeline entry).
  • A read-only audit of every user-reachable page_body_segmented refusal (about 900 probes on both engines) drove the 2r refusal fixes.

Contract golden changes

  • Export-surface goldens (test/fixtures/goldens/exports/{runtime,types}.json) gain keywordBoundedRead, the one engine member added so search/dedup.ts segmentRegions runs its manifest probe and region read under the keyword knob's statement_timeout.

Known gaps (not in this PR)

  • applySegmentedBody (segmented-apply.ts:184) runs hasStaleChunks inside 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 after purge_fact a segmented page's file on disk keeps the purged claim. It must be fixed before the flip.

  • Switch-on blocker: the projection claim's renew in buildClaimedProjection refreshes 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_version on a segmented page refuses until PR 3 (Garry accepted; BEHAVIOR row added).

  • edit_page and capture onto 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-recovery lists a large-file recovery as refused; the resident writer finishes it.

  • Older binaries fail closed (unexpected_staging_bytes) on a recovery record that carries staging.before.

  • The gate can't see an older binary that writes without managed persistence and leaves no request or ingest row; pages_body_storage_chk still fails old whole-body writes closed.

  • Dropping retired_generation for 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.

Open in Capy

garrytan and others added 30 commits October 15, 2026 21:10
…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>
…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>
garrytan and others added 15 commits October 17, 2026 09:36
…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>
…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>
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>
…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>
garrytan and others added 5 commits October 17, 2026 13:58
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>
@capy-ai capy-ai Bot changed the title Wave B PR 2: segmented reads, privacy, forget/purge, export/backup, derived passes (switch stays off) v0.60.290.0 Wave B PR 2: segmented reads, privacy, forget/purge, export/backup, derived passes (switch stays off) Oct 17, 2026
…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>
@capy-ai
capy-ai Bot merged commit 0fb905d into master Oct 17, 2026
154 of 155 checks passed
@github-actions github-actions Bot mentioned this pull request Oct 17, 2026
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>
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