Skip to content

Commit 152a385

Browse files
committed
test: validate fold against real session corpus
1 parent 204f65a commit 152a385

12 files changed

Lines changed: 307 additions & 35 deletions

File tree

‎docs/design.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
CodexFold is an unofficial, local-first tool for measuring, deduplicating, storing, and recovering Codex session rollouts. The public project is standalone and has no private control-plane dependency.
66

7+
The optimization mechanisms and lifecycle boundaries are defined in [the fold taxonomy](fold-taxonomy.md). Real-corpus storage evidence is recorded separately in [the v0.3 validation](validation-real-corpus-v0.3.md).
8+
79
The current release capability is `storage-engine`. Transparent normal-path session access is a separate product stage defined by `docs/superpowers/specs/2026-07-11-transparent-session-filesystem-design.md`.
810

911
## Safety

‎docs/fold-taxonomy.md‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# CodexFold Optimization Taxonomy
2+
3+
CodexFold has one storage objective: preserve every rollout byte while storing exact duplicate byte content once. This applies within one long session, across unrelated sessions, and across fork branches. Cross-session or cross-client boundaries are not prerequisites for reuse.
4+
5+
## Byte-Preserving Content Reuse
6+
7+
| Mechanism | Scope | Maximum saving | Current boundary |
8+
| --- | --- | --- | --- |
9+
| Exact raw JSON string fields | Identical large string tokens at any position | Approaches `1 - 1/N` for `N` copies, before compression | Fold V1 stores field objects explicitly |
10+
| Exact JSONL records | Complete identical physical records | Approaches `1 - 1/N` for `N` copies | Scan and containment measure records explicitly; Fold V1 does not yet emit an atomic record object |
11+
| Content-defined chunks | Repeated byte ranges at arbitrary offsets, including non-prefix fork content | Approaches `1 - 1/N` for repeated chunks | Fold V1 stores residual CDC objects explicitly |
12+
| Shared fork history | Any exact fields, records, or chunks shared by useful branches | Same as the underlying reuse layers | Strict byte-prefix ancestry is optional evidence, never a storage requirement |
13+
14+
The three measurement layers overlap. Their reported duplicate bytes must never be added together. The committed fold manifest is the authoritative union representation.
15+
16+
## Encoding And Physical Layout
17+
18+
| Mechanism | Purpose | Maximum saving | Current boundary |
19+
| --- | --- | --- | --- |
20+
| Zstandard object encoding | Compress each unique object | Data-dependent; near zero on incompressible bytes and near 100% on highly repetitive bytes | Exact raw SHA-256 is verified after decompression |
21+
| Immutable packfiles and index | Replace hundreds of thousands of loose files and support efficient range reads | Removes filesystem block/inode overhead; it is not logical deduplication | Pack doctor verifies every object and complete packed manifest reconstruction |
22+
| Base plus append delta | Keep an active session writable without materializing the full folded base | Stores only the new tail until compaction | Append is durable; truncate and random write transition to copy-on-write |
23+
| Generation compaction and GC | Fold stable deltas and remove unreferenced objects, obsolete generations, and abandoned temporary files | Limited to proven unreferenced physical bytes | Never removes a referenced object or current recoverable generation |
24+
25+
## Session Lifecycle Operations
26+
27+
These operations are not compression algorithms and must be reported separately.
28+
29+
| Operation | Meaning | Physical saving |
30+
| --- | --- | --- |
31+
| Archive a branch | Move an explicitly selected useful-but-inactive session out of the active list while preserving recovery | Zero by itself |
32+
| Keep both fork branches | Share exact objects while each branch retains its own independent writable tail | Duplicate content is stored once; both histories remain openable |
33+
| Remove an exactly contained branch | Delete an already archived session only after its applicable record sequence is directly proven inside a retained session and a recovery unfold succeeds | Can approach the complete contained source size when all objects are already shared |
34+
| Retire native and loose fallback copies | Remove full native sources, migration snapshots, or loose objects only after platform and per-session recovery gates pass | This is the step that turns logical reuse into actual disk reclamation |
35+
36+
Fork ancestry, age, title, size, or similarity never proves that a branch is useless or contained.
37+
38+
## Content-Changing Cleanup
39+
40+
Prompt removal, repeated-message cleanup, repair, reconciliation, summarization, and semantic compaction can reduce a rollout further, but they change bytes. They are separate explicit workflows with separate outputs and recovery rules. Fold, pack, migration, compaction, enrollment, and GC never run them implicitly.
41+
42+
## Transparent Access
43+
44+
The transparent filesystem is the delivery mechanism, not another compression layer. Its contract is:
45+
46+
- unmodified Codex Desktop and CLI open normal JSONL paths without manual materialization;
47+
- a managed file exposes byte-identical reads and the file operations Codex actually uses;
48+
- active sessions append to durable deltas and forks remain independently writable;
49+
- existing and newly stable sessions can be enrolled automatically under policy;
50+
- compatibility quarantine and rollback preserve a verified native current copy.
51+
52+
## Physical-Saving Claim
53+
54+
CodexFold reports these numbers separately:
55+
56+
1. Logical rollout bytes.
57+
2. Unique raw object bytes after deduplication.
58+
3. Compressed pack bytes and metadata.
59+
4. Retained native sources and fallbacks.
60+
5. Loose objects, old generations, recovery artifacts, and temporary files.
61+
6. Projected reclaimable bytes and actual reclaimed bytes.
62+
63+
No physical-saving claim is valid while equivalent full native or loose copies still occupy disk.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Real Corpus Fold Validation
2+
3+
## Scope
4+
5+
This validation used local, read-only Codex rollout sources and an isolated store. Reports contain aggregate counts only; no session content, identifiers, credentials, titles, or workspace paths are included.
6+
7+
The representative set contained 58 sessions and 16.01 GB of source bytes. It included large active sessions, large archived sessions, multiple workspaces, and every session participating in the 23 recorded fork edges. One actively changing source was correctly rejected before manifest commit. The remaining 57 sessions contributed 15.47 GB to the fold store.
8+
9+
## Duplicate Measurements
10+
11+
| Layer | Observations | Unique objects | Duplicate bytes |
12+
| --- | ---: | ---: | ---: |
13+
| Exact large fields | 475,835 | 174,643 | 7,534,867,486 |
14+
| Exact complete records | 4,876,474 | 4,707,166 | 297,477,994 |
15+
| Content-defined chunks | 628,948 | 398,775 | 4,567,543,137 |
16+
17+
These layers overlap and are not additive. The scan processed 16,007,421,613 bytes in 302.9 seconds with a 1.06 GB disposable disk index. It counted 153 JSON records that could not participate in field parsing; their bytes still participated in the exact-record and CDC layers.
18+
19+
All 23 recorded fork parent/child pairs had explicit ancestry but no strict shared record prefix, shared exact record, or exact containment. This confirms that fork graph edges and strict prefixes cannot serve as the storage model. A separate linear digest pass over all 1,881 registered sessions found no meaningful pair with an identical applicable record stream; the only duplicate digest represented sessions with no records after `session_meta`.
20+
21+
## Fold, Pack, And Recovery
22+
23+
| Metric | Result |
24+
| --- | ---: |
25+
| Folded source bytes | 15,472,621,697 |
26+
| Manifest references | 992,953 |
27+
| Unique referenced objects | 644,289 |
28+
| Unique raw object bytes | 7,722,034,851 |
29+
| Packed data bytes | 3,939,856,668 |
30+
| Pack physical bytes, including index | 4,136,652,800 |
31+
| Manifest physical bytes | 225,607,680 |
32+
33+
Raw deduplication reduced the logical corpus by 50.09%. Zstandard pack encoding reduced unique raw object bytes by a further 48.98%. If native sources and loose objects are safely retired, the pack plus manifest representation is 28.19% of the folded source bytes, a projected 71.81% physical reduction for this corpus.
34+
35+
The best order-dependent incremental session added 2.45% of its source bytes to the existing object store; the worst added 88.59%; the median added 25.32%. These figures depend on corpus order and are not standalone compression ratios.
36+
37+
Verification completed with:
38+
39+
- 57 of 57 manifests reconstructed from loose objects with exact size and SHA-256.
40+
- 644,289 of 644,289 packed objects verified.
41+
- 57 of 57 complete manifests reconstructed through the pack-only reader with exact size and SHA-256.
42+
- 57 of 57 explicit `unfold` operations independently checked by size and SHA-256.
43+
- Zero retained unfold output files after the batch.
44+
45+
One rejected active fold left 38,604 unreferenced loose objects. GC identified and removed all of them, reclaiming 249,114,624 physical bytes while preserving every referenced object.
46+
47+
## Open Gates
48+
49+
- Fold V1 does not represent a complete repeated JSONL record as one atomic object. Exact record duplication is measured, and its bytes can be reused through field and CDC objects, but an atomic record layer requires a compatible hierarchical or precomputed Fold V2 representation rather than weakening field/chunk reuse.
50+
- Packed and native-source retirement are not yet an automatic production policy. Until retirement is implemented and its gates pass, aggregate logical savings do not equal current disk reclamation.
51+
- Real-scale pack construction peaked at approximately 1.6 GB RSS and pack doctor at approximately 624 MB RSS. These maintenance operations need bounded-memory index construction before bulk production use.

‎internal/cli/fs_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ func TestPackBuildAndDoctorCommandsUseFoldStore(t *testing.T) {
587587
t.Fatalf("pack doctor: %v", err)
588588
}
589589
var doctor pack.DoctorResult
590-
if err := json.Unmarshal(output.Bytes(), &doctor); err != nil || doctor.IssueCount != 0 {
590+
if err := json.Unmarshal(output.Bytes(), &doctor); err != nil || doctor.IssueCount != 0 || doctor.ManifestCount != 1 || doctor.VerifiedManifestCount != 1 {
591591
t.Fatalf("unexpected pack doctor: %#v err=%v output=%s", doctor, err, output.String())
592592
}
593593
}

‎internal/cli/pack.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func newPackDoctorCommand() *cobra.Command {
5454
var jsonOutput bool
5555
command := &cobra.Command{
5656
Use: "doctor",
57-
Short: "Verify the active packed object generation",
57+
Short: "Verify packed objects and complete manifest reconstruction",
5858
Args: cobra.NoArgs,
5959
RunE: func(command *cobra.Command, _ []string) error {
6060
home, err := codex.ResolveHome(codexHome)
@@ -68,7 +68,7 @@ func newPackDoctorCommand() *cobra.Command {
6868
if jsonOutput {
6969
return writeJSON(command, result)
7070
}
71-
_, err = fmt.Fprintf(command.OutOrStdout(), "generation=%s objects=%d verified=%d issues=%d\n", result.Generation, result.ObjectCount, result.VerifiedCount, result.IssueCount)
71+
_, err = fmt.Fprintf(command.OutOrStdout(), "generation=%s objects=%d verified=%d manifests=%d verified_manifests=%d issues=%d\n", result.Generation, result.ObjectCount, result.VerifiedCount, result.ManifestCount, result.VerifiedManifestCount, result.IssueCount)
7272
return err
7373
},
7474
}

‎internal/fold/doctor.go‎

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,30 +37,33 @@ type loadedManifest struct {
3737

3838
func Doctor(ctx context.Context, storeDir string) (DoctorResult, error) {
3939
result := DoctorResult{StoreDir: storeDir, Issues: make([]DoctorIssue, 0)}
40-
manifests, loadIssues, err := loadAllManifests(storeDir)
41-
if err != nil {
42-
return DoctorResult{}, err
43-
}
44-
result.ManifestCount = len(manifests) + len(loadIssues)
45-
result.Issues = append(result.Issues, loadIssues...)
4640
store := NewObjectStore(storeDir)
4741
unique := make(map[string]ObjectRef)
48-
for _, loaded := range manifests {
49-
manifest := loaded.Manifest
42+
err := walkManifestPaths(storeDir, func(path string) error {
5043
if err := ctx.Err(); err != nil {
51-
return DoctorResult{}, err
44+
return err
45+
}
46+
result.ManifestCount++
47+
manifest, err := LoadManifestPath(path)
48+
if err != nil {
49+
result.Issues = append(result.Issues, DoctorIssue{Scope: "manifest", Path: path, Error: err.Error()})
50+
return nil
5251
}
5352
for _, part := range manifest.Parts {
5453
result.ObjectReferenceCount++
5554
unique[part.Object.SHA256] = part.Object
5655
}
5756
if err := verifyStoredManifest(ctx, store, manifest); err != nil {
5857
result.Issues = append(result.Issues, DoctorIssue{
59-
Scope: "manifest", Path: loaded.Path, Error: err.Error(),
58+
Scope: "manifest", Path: path, Error: err.Error(),
6059
})
6160
} else {
6261
result.VerifiedManifestCount++
6362
}
63+
return nil
64+
})
65+
if err != nil {
66+
return DoctorResult{}, err
6467
}
6568
result.UniqueObjectCount = len(unique)
6669
for digest, ref := range unique {
@@ -93,6 +96,26 @@ func Doctor(ctx context.Context, storeDir string) (DoctorResult, error) {
9396
return result, nil
9497
}
9598

99+
func walkManifestPaths(storeDir string, visit func(path string) error) error {
100+
manifestDir := filepath.Join(storeDir, "manifests")
101+
err := filepath.WalkDir(manifestDir, func(path string, entry os.DirEntry, walkErr error) error {
102+
if walkErr != nil {
103+
return walkErr
104+
}
105+
if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" {
106+
return nil
107+
}
108+
return visit(path)
109+
})
110+
if errors.Is(err, os.ErrNotExist) {
111+
return nil
112+
}
113+
if err != nil {
114+
return fmt.Errorf("read manifest directory: %w", err)
115+
}
116+
return nil
117+
}
118+
96119
func loadAllManifests(storeDir string) ([]loadedManifest, []DoctorIssue, error) {
97120
manifestDir := filepath.Join(storeDir, "manifests")
98121
manifests := make([]loadedManifest, 0)

‎internal/fold/gc.go‎

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ type GCResult struct {
2525

2626
func GC(ctx context.Context, storeDir string, apply bool) (GCResult, error) {
2727
result := GCResult{StoreDir: storeDir, DryRun: !apply}
28-
_, issues, err := loadAllManifests(storeDir)
28+
_, invalidManifests, err := referencedManifestObjects(ctx, storeDir, false)
2929
if err != nil {
3030
return GCResult{}, err
3131
}
32-
if len(issues) > 0 {
33-
return GCResult{}, fmt.Errorf("refusing GC with %d invalid manifest(s)", len(issues))
32+
if invalidManifests > 0 {
33+
return GCResult{}, fmt.Errorf("refusing GC with %d invalid manifest(s)", invalidManifests)
3434
}
3535
before, err := storage.Scan(ctx, storage.Options{StoreDir: storeDir, AllowMetadataIssues: true})
3636
if err != nil {
@@ -42,18 +42,12 @@ func GC(ctx context.Context, storeDir string, apply bool) (GCResult, error) {
4242
}
4343
result.Storage = storageResult
4444
result.ProjectedReclaimableBytes = storageResult.ProjectedReclaimableBytes
45-
manifests, issues, err := loadAllManifests(storeDir)
45+
referenced, invalidManifests, err := referencedManifestObjects(ctx, storeDir, true)
4646
if err != nil {
4747
return GCResult{}, err
4848
}
49-
if len(issues) > 0 {
50-
return GCResult{}, fmt.Errorf("refusing loose-object GC with %d invalid manifest(s)", len(issues))
51-
}
52-
referenced := make(map[string]struct{})
53-
for _, loaded := range manifests {
54-
for _, part := range loaded.Manifest.Parts {
55-
referenced[part.Object.SHA256] = struct{}{}
56-
}
49+
if invalidManifests > 0 {
50+
return GCResult{}, fmt.Errorf("refusing loose-object GC with %d invalid manifest(s)", invalidManifests)
5751
}
5852
result.Referenced = len(referenced)
5953
err = walkObjectFiles(storeDir, func(path string, info os.FileInfo) error {
@@ -88,3 +82,25 @@ func GC(ctx context.Context, storeDir string, apply bool) (GCResult, error) {
8882
}
8983
return result, nil
9084
}
85+
86+
func referencedManifestObjects(ctx context.Context, storeDir string, collect bool) (map[string]struct{}, int, error) {
87+
referenced := make(map[string]struct{})
88+
invalid := 0
89+
err := walkManifestPaths(storeDir, func(path string) error {
90+
if err := ctx.Err(); err != nil {
91+
return err
92+
}
93+
manifest, err := LoadManifestPath(path)
94+
if err != nil {
95+
invalid++
96+
return nil
97+
}
98+
if collect {
99+
for _, part := range manifest.Parts {
100+
referenced[part.Object.SHA256] = struct{}{}
101+
}
102+
}
103+
return nil
104+
})
105+
return referenced, invalid, err
106+
}

‎internal/jsonraw/spans.go‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package jsonraw
33
import (
44
"bytes"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"io"
89
"strconv"
910
"strings"
1011
)
1112

13+
var ErrMultipleValues = errors.New("multiple JSON values in one record")
14+
1215
type StringSpan struct {
1316
Path string
1417
Start int
@@ -24,7 +27,7 @@ func FindStringSpans(data []byte, minRawBytes int64) ([]StringSpan, error) {
2427
}
2528
if _, err := decoder.Token(); err != io.EOF {
2629
if err == nil {
27-
return nil, fmt.Errorf("multiple JSON values in one record")
30+
return nil, ErrMultipleValues
2831
}
2932
return nil, err
3033
}

‎internal/jsonraw/spans_test.go‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
package jsonraw
22

3-
import "testing"
3+
import (
4+
"errors"
5+
"testing"
6+
)
7+
8+
func TestFindStringSpansRejectsMultipleJSONValues(t *testing.T) {
9+
_, err := FindStringSpans([]byte(`{"a":"one"}{"b":"two"}`), 1)
10+
if !errors.Is(err, ErrMultipleValues) {
11+
t.Fatalf("error = %v, want ErrMultipleValues", err)
12+
}
13+
}
414

515
func TestFindStringSpansReturnsExactNestedRawToken(t *testing.T) {
616
line := []byte(`{"payload":{"items":[{"text":"small"},{"text":"large\\nvalue"}]}}`)

0 commit comments

Comments
 (0)