Skip to content

Compact loose objects into indexed packs without changing their identities - #150

Open
unbraind wants to merge 45 commits into
mainfrom
feat/indexed-pack-storage
Open

unbraind wants to merge 45 commits into
mainfrom
feat/indexed-pack-storage

Conversation

@unbraind

@unbraind unbraind commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Loose objects can now be compacted into indexed packs while retaining their uncompressed content identities. The typed SDK and pm vcs compact support both representations; GC rewrites survivors, and interrupted publication resumes with exact receipts.

Fixes #151.

Physical namespace identity is captured when a storage handle is constructed. The documented new ObjectStore(".pmvcs/objects").compact() now creates a pack and completes retirement. Eleven filesystem-owning constructors were audited; inventories, pack caches, lease keys and durable deletion sets use captured absolute coordinates. Explicit read hints, scan exclusions, lazy iterators and returned purge/deletion authority bind relative paths at API entry. Later cwd changes cannot redirect them. Equivalent spellings compare through the same captured base; a caller changing a live request's namespace fields receives a typed refusal before deletion.

Every object reader uses one resolver with eight bounded attempts across loose/pack transitions. Streams retain their opened descriptor and check that descriptor after retirement. One handle helper closes deterministically on completion, early return, error, destroy and abort. One platform-aware durability helper owns flushes and namespace retirement; Windows uses file flushes and synchronous namespace acknowledgment, with its documented weaker power-loss guarantee.

Destructive operations validate all inputs and preconditions, persist a revision-bound exact deletion set, then retire only those members and finalize a receipt. Invalid requests delete nothing. Recovery keeps the original set. Changed eligibility or reachability durably supersedes obsolete authority before re-planning, so interrupted GC can progress when a candidate becomes reachable. Working-tree and lease cleanup preserve original inode authority. Completed receipts become historical when their root ends; pending entries resolve or cancel with a typed reason.

Obliteration requires every regular file under every current object root, regardless of name or extension, to be accountable native storage and payload-free. Unknown, renamed, copied, partial or truncated files refuse unverifiable; explicit --purge-unaccounted records an identity-bound set, returns a receipt and re-verifies. Native decoding, raw inspection and encoded-payload inspection use the same pinned descriptor, followed by descriptor/path and whole-catalogue identity checks. Exhausting a depth, decode, byte, stream, context, count or time bound, or encountering a decoding error, yields unverified with its bound. Incomplete integrity, reachability, checkpoint or receipt evidence never becomes a positive certificate.

Acceptance / regression Evidence
Stable identities; range reads and GC across representations Random-corpus differential tests before/after GC and every compaction crash step; unchanged logical hashes and fragment ranges.
Relative roots and exact retirement Eight path-identity tests; 32 seeded stores, four spellings, 96 live identities, 4,032 cwd-changing publication boundaries; 74 deletion-intent interruption cases and five namespace-mutation/equivalence cases.
Every reader survives transitions without resurrection 25 APIs; original 6,750 live/erased checks, 700 resolver schedules and 406 external-retirement injections; an additional 27,000 checks across 540 relative-root/cwd transition boundaries.
Real process death and replay 90 additional relative-root SIGKILL cuts: 13 compaction, 32 unpack, 45 GC; two completed replays per cut, unchanged shadow trees and no duplicates.
Deterministic resource lifetime 200 API/representation/exit-mode cells; 80,000 reads under a 128-descriptor limit, zero EMFILE. Current targeted resource/alias/environment compatibility set passes 8/8.
Accountable erasure and replacement safety 160 seeded stores, 190 truncation/overwrite cases, 32 replacement cells, 168 foreign-file cases over 14 encodings, 72 nested-codec cases and 41 late-copy schedules. Eight nested base64 layers refuse for loose and packed storage.
Receipt/root lifecycle 512 seeded sequences, 6,144 operations, 2,048 verdicts and 768 crash cases, including removal, prune and same-name recreation.
Validate before deleting 515 invalid requests across 16 corpora and every purge/recovery flag combination; tree hashes/tripwires; 512 valid stores and 1,306 operations.
Obsolete-plan recovery and fail-closed bounds 128 seeded changed-eligibility transitions, 465 successor deletions and 68 native SIGKILL cuts; per-verifier bound/fault tests, including negative records beyond rendered pages.
Installed Node/Bun consumers and agent output Six built/npm-packed SDK and project-installed CLI tests pass, including relative-root maintenance, JSON/default TOON and non-git trackers.
Owner issue and hermetic provenance Separate commits aee95e4, c56d797, 1d372bd: await fixture owners before retrying teardown; explicit environments, ambient-agent injection and provenance assertions. Audit covers 103 isolated entrypoints and 170 explicit child launches.

Behavioral revert proofs are recorded in pm-vcs-phase6packs. For the namespace class, 22 isolated behavioral reversions each fail the targeted regression; reverting the private served-owner constructor also fails the package-wide constructor invariant. Restored path and alias tests pass. Earlier reader, durability, deterministic-close, accountable-erasure, lifecycle, validate-first, named-bound and obsolete-GC-plan revert proofs remain recorded. Existing tests and thresholds were preserved.

Recovery journals now flush their file, parent, and every ancestor before destructive mutation, including separate newly created control and journal roots. Source and destination parents are flushed after rename/unlink. Destination-keyed publication receipts complete the original interrupted rename when retries use a new temporary; released source-keyed receipts migrate under their original lease. Descriptor-owned process leases make ref, log, registry and bisect recovery safe after owner publication. Foreign and live owners are preserved with typed remedies.

Regression evidence includes both durability capabilities, 17 competing publication schedules, 16 directory-recovery schedules, eleven foreign-directory classes, released source-keyed receipt compatibility and forced fail-closed receipt budgets. Behavioral reverts for ancestry, interrupted rename reconciliation, empty-directory recovery and copied receipt capabilities fail; restored implementations pass. Reverting additive audit preparation also fails its new regression (0 audit objects instead of 1), then passes after restoration. All proofs are recorded in the PM item.

Erasure prepares immutable audit objects before freezing purge membership and activates denial only after intent publication. This prevents an interrupted purge from creating an unplanned audit temporary. Existing native SIGKILL tests and their exact receipt assertions remain intact. Caller-specific bisect lock remedies and ref expected/current details are retained.

The power-loss harness now covers seventeen publication/retirement/recovery sequences. Native namespace-only link, rename and unlink restoration preserves content mtime while reflecting inode metadata changes; both cached and uncached oracles check this rule. The completed recovery matrix retains every native fault cut and executes two convergent replays for each distinct crash image. The new completed sweeps cover 3,430 repack boundaries / 383 images, 2,980 packed-GC boundaries / 323 images, 2,304 obliteration boundaries / 272 images and 553 native process-stage recovery boundaries / 66 images: 9,267 boundaries and 1,044 full crash images in total, with two recovery replays each. Earlier completed metadata/ref/compaction/unpack/lease/prune/log and journal-recovery evidence remains recorded in the PM item. Unfinished or superseded sweeps are not claimed as passes.

Resuming the prior CI exposed six failures on each Node gate (2,211/2,217 passing). Native lease stages are now fully parsed and identity-bound; orphan retirement uses durable exact-set authority under the new lease and preserves live/foreign owners. Erasure deduplicates its operation log inside that same log lease. Staging alias cleanup settles before a new pack deletion snapshot, with pinned post-unlink identity evidence so peer replacements still refuse. Public recovery takes the store writer lease. GC resumes its recorded survivor rewrite even when interruption left only loose live objects, and preview/apply share exact-key checkpoint inspection with named verification bounds and explicit foreign-record outcomes. The reader roster classifies the existing additive audit writer without changing its 25-reader enumeration. Eight isolated function/condition revert proofs each fail once and pass after restoration; the PM item records them.

Validation on pushed head 3865a97abf8369b47edce223a90a5ab08137218b: source/test typecheck, lint, 554 documented declarations, zero duplication, strict PM health and changelog checks pass. PM-linked corpus/stream/CLI pack tests pass 5/5; the current regression selection passes 17/17, including 39 competing-owner transitions and three real SIGKILL modes. Rebuilt and npm-packed Node/Bun SDK consumers plus the installed project CLI pass 6/6, zero skipped; package privacy inspects 312 files with zero findings. Committed self-host acceptance: passed after regeneration and commit; all three commits in this resume completed their required write/commit/accept cycle.

Current-head CI: pending at the 19:07 UTC observation cutoff within the requested 45-minute cap. Windows GC passed 2/2; CodeQL passed 1/1. Both Node 22/26 npm release gates are running and their Bun gates follow: 0/4 full release gates complete. Current main is merged through bef2611 by two-parent merge fc403e4 (parents 8b945eb and bef2611); no rebase or force push. Full npm/Bun release certification remains pending until the exact-head gates pass. Existing tests, thresholds and dependency pins are unchanged.

Real public-fleet validation used a read-only tracker copy at c7790d45aab22a3a28b4d44955a592dfc06e715e, outside Git: 330 tracker files, 343 objects and 686 matching loose/packed reads.

Measurement Loose Packed
Object files 343 1
Compressed object bytes 1,357,191 1,262,523
Allocated object bytes 2,187,264 1,265,664
Median read, ms 0.255666 0.266632
p95 read, ms 3.250911 1.211096

301 samples per representation on a busy host; no median speed-improvement claim. Durable audit metadata is measured separately. Reproduction: scripts/pack-benchmark.ts, docs/indexed-pack-benchmark.json. Format, SDK example and remedies: README, docs/indexed-packs.md, docs/destructive-operations.md.

PM items remain unclosed; the feature remains in_progress and the implementation claim is released: pm-vcs-phase6packs, pm-vcs-annotationteardown, pm-vcs-testenv; parent pm-vcs-b7cb. Optional erasure-fingerprint transport is tracked in pm-vcs-erasureprooftransport.

SDK capability audit: existing item lookup, list/history, locks, settings, transactions and renderers remain on the PM SDK. Public asynchronous commitWorkspaceTransaction exists; there is no synchronous descriptor-bound compressed-range primitive. PmClient has item-closing methods but no client-disposal/pending-worker-drain API. A capped listAssuranceVerdicts query refuses certification. Native fingerprint transport is a package follow-up, not an SDK gap. No implementation requirement is deferred as an SDK gap; final CI certification remains pending.

Limits: packed GC/erasure needs temporary loose capacity. Verification proves a stable inspected namespace under its lease; it does not wipe sectors or external backups. Receipts retain their audit history. No merge or release.

@sourcery-ai

sourcery-ai Bot commented Oct 25, 2026

Copy link
Copy Markdown

Sorry @unbraind, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 2 days and 22 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: dc98ff2a-be24-4903-a386-2bbd502bb417
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Oct 25, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR introduces crash-safe, lease-coordinated compaction of verified loose objects into bounded SHA-256-addressed indexed packs, updates reads, GC, erasure, CLI/SDK reporting and documentation to support both representations, and adds extensive integrity, concurrency, interruption, and benchmark coverage.

Sequence diagram for crash-safe indexed pack compaction

sequenceDiagram
    participant CLI as VcsCommand
    participant Store as ObjectStore
    participant Lease as SharedWriteLease
    participant Packs as PackStorage
    participant FS as Filesystem

    CLI->>Store: compact()
    Store->>Lease: withWriteLock()
    Lease->>Packs: compact(store)
    Packs->>Store: walkLooseInventory()
    Packs->>Packs: publish(bytes, destination, store)
    Packs->>FS: write and fsync staging payload
    Packs->>FS: linkSync published pack
    Packs->>FS: retire loose sources
    Packs-->>Lease: PackReport
    Lease-->>Store: PackReport
    Store-->>CLI: PackReport
Loading

Sequence diagram for verified packed object reads

sequenceDiagram
    participant Caller
    participant Store as ObjectStore
    participant Packs as PackStorage
    participant Pack as IndexedPack

    Caller->>Store: read(id)
    Store->>Packs: read(id)
    Packs->>Pack: validate index and locate range
    Packs->>Pack: read compressed member range
    Packs->>Packs: inflate and parseFramedObject()
    Packs->>Packs: hashObject()
    Packs-->>Store: StoredObject
    Store-->>Caller: StoredObject
Loading

File-Level Changes

Change Details Files
Adds immutable indexed-pack storage while preserving logical object addresses and verified range reads.
  • Introduces a bounded pack format with sorted indexes, independently compressed frames, physical checksums, and strict validation.
  • Adds pack-aware presence checks, typed reads, streaming reads, inventory entries, and loose fallback/retry behavior.
  • Publishes packs through owned durable staging and exclusive linking, with duplicate retirement, peer-result handling, and crash recovery.
  • Exposes typed PackRefusal errors and bounded compaction receipts.
engine/packs.ts
engine/objects.ts
engine/object-error.ts
index.ts
docs/indexed-packs.md
Integrates compaction, unpacking, packed garbage collection, and erasure with existing retention and deletion guarantees.
  • Adds ObjectStore.compact() and unpack() under the shared writer lease.
  • Extends GC inventory and accounting to packed representations, materializes verified loose survivors/candidates, and rewrites remaining packs.
  • Unpacks and verifies packs before authorized erasure so the existing durable deletion proof remains applicable.
  • Preserves retention roots, denial behavior, and safe handling of interrupted transitions.
engine/gc.ts
engine/erasure.ts
engine/objects.ts
Adds the public CLI, SDK, documentation, and operational benchmark surface for indexed packs.
  • Registers vcs compact with host rendering and JSON/TOON receipts.
  • Documents pack layout, recovery, corruption behavior, GC, erasure, SDK usage, and size limitations.
  • Adds a read-only public-tracker benchmark reporting aggregate storage and verified-read metrics.
vcs-commands.ts
README.md
docs/indexed-packs.md
docs/indexed-pack-benchmark.json
docs/scale-history-and-gc.md
scripts/pack-benchmark.ts
Adds broad validation for representation equivalence, integrity, crash recovery, concurrency, and command behavior.
  • Covers deterministic differential reads, fragment ranges, mixed representations, oversized objects, and packed GC.
  • Exercises SIGKILL recovery and shared-lease contention across compact, unpack, and GC boundaries.
  • Validates malformed packs, ownership cleanup, peer publication outcomes, erasure denial, bounded receipts, inventory ranges, and Git-free CLI execution.
  • Updates command registration coverage for the new command.
test/packs.test.ts
test/helpers/pack-worker.ts
test/vcs-commands.test.ts
selfhost.bundle
Updates project-management evidence for the pack-storage phase.
  • Records implementation and validation progress in the PM feature and history artifacts.
.agents/pm/features/pm-vcs-phase6packs.toon
.agents/pm/history/pm-vcs-phase6packs.jsonl

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@unbraind unbraind changed the title Compact loose objects into verified indexed packs Compact objects into indexed packs with safe concurrent reads Oct 25, 2026

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

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.

Node 22 release gate fails in annotation fixture cleanup with ENOTEMPTY on PM locks

2 participants